享元模式

简介

定义: 运用共享技术有效地支持大量细粒度的对象。主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

使用场景:

  • 文本编辑器的字符对象

  • 游戏中的大量粒子、子弹

  • String常量池

  • Integer缓存(-128到127)

实现要点:

  • 内部状态(可共享)

  • 外部状态(不可共享)

  • 享元工厂管理对象池

UML

代码示例

  1. 创建一个享元接口

public interface CharacterFlyweight {
    
    /**
     * 显示字符
     * @param fontSize 字体大小(外部状态)
     * @param color 颜色(外部状态)
     * @param position 位置(外部状态)
     */
    void display(int fontSize, String color, String position);
}
  1. 享元实现

public class ConcreteCharacter implements CharacterFlyweight {
    
    /**
     * 内部状态:字符本身(可共享,不变)
     */
    private final char character;
    
    /**
     * 构造函数
     * @param character 字符(内部状态)
     */
    public ConcreteCharacter(char character) {
        this.character = character;
        System.out.println("✨ 创建享元对象: '" + character + "'");
    }
    
    @Override
    public void display(int fontSize, String color, String position) {
        // 内部状态(character)+ 外部状态(fontSize, color, position)
        System.out.println(String.format("字符: '%c' | 字号: %d | 颜色: %s | 位置: %s", 
                                       character, fontSize, color, position));
    }
    
    /**
     * 获取字符
     */
    public char getCharacter() {
        return character;
    }
}
  1. 享元工厂

public class CharacterFactory {
    
    /**
     * 享元池:存储已创建的享元对象
     * key: 字符, value: 享元对象
     */
    private Map<Character, CharacterFlyweight> flyweightPool;
    
    /**
     * 单例模式:确保只有一个工厂实例
     */
    private static CharacterFactory instance = new CharacterFactory();
    
    /**
     * 私有构造函数
     */
    private CharacterFactory() {
        flyweightPool = new HashMap<>();
    }
    
    /**
     * 获取工厂实例
     */
    public static CharacterFactory getInstance() {
        return instance;
    }
    
    /**
     * 获取字符享元对象
     * 如果池中存在则返回,否则创建新对象并放入池中
     */
    public CharacterFlyweight getCharacter(char c) {
        CharacterFlyweight character = flyweightPool.get(c);
        
        if (character == null) {
            // 池中不存在,创建新对象
            character = new ConcreteCharacter(c);
            flyweightPool.put(c, character);
        } else {
            System.out.println("♻️ 复用享元对象: '" + c + "'");
        }
        
        return character;
    }
    
    /**
     * 获取享元池大小
     */
    public int getPoolSize() {
        return flyweightPool.size();
    }
    
    /**
     * 清空享元池
     */
    public void clearPool() {
        flyweightPool.clear();
        System.out.println("🗑️ 享元池已清空");
    }
    
    /**
     * 显示享元池状态
     */
    public void showPoolStatus() {
        System.out.println("\n📊 享元池状态:");
        System.out.println("   池中对象数量: " + flyweightPool.size());
        System.out.println("   已创建的字符: " + flyweightPool.keySet());
    }
}
  1. 客户端

public class Editor {
    
    /**
     * 字符列表(存储外部状态)
     */
    private List<CharacterContext> characters;
    
    private CharacterFactory factory;
    
    public Editor() {
        characters = new ArrayList<>();
        factory = CharacterFactory.getInstance();
    }
    
    /**
     * 插入字符
     */
    public void insertCharacter(char c, int fontSize, String color, String position) {
        // 从工厂获取享元对象
        CharacterFlyweight flyweight = factory.getCharacter(c);
        
        // 创建上下文,存储外部状态
        CharacterContext context = new CharacterContext(flyweight, fontSize, color, position);
        characters.add(context);
    }
    
    /**
     * 显示文档内容
     */
    public void display() {
        System.out.println("\n📄 文档内容:");
        System.out.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        for (CharacterContext context : characters) {
            context.display();
        }
        System.out.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    }
    
    /**
     * 获取字符数量
     */
    public int getCharacterCount() {
        return characters.size();
    }
    
    /**
     * 清空文档
     */
    public void clear() {
        characters.clear();
        System.out.println("📄 文档已清空");
    }
    
    /**
     * 字符上下文类 - 存储外部状态
     */
    private static class CharacterContext {
        private CharacterFlyweight flyweight;
        private int fontSize;
        private String color;
        private String position;
        
        public CharacterContext(CharacterFlyweight flyweight, int fontSize, String color, String position) {
            this.flyweight = flyweight;
            this.fontSize = fontSize;
            this.color = color;
            this.position = position;
        }
        
        public void display() {
            flyweight.display(fontSize, color, position);
        }
    }
}
  1. 使用方式

public class Test {

    public static void main(String[] args) {
        System.out.println("=== 享元模式 - 文本编辑器示例 ===\n");

        // 创建文本编辑器
        Editor editor = new Editor();
        CharacterFactory factory = CharacterFactory.getInstance();

        System.out.println("--- 场景1:输入文本 \"HELLO\" ---");
        // 输入 HELLO
        editor.insertCharacter('H', 12, "红色", "(0,0)");
        editor.insertCharacter('E', 12, "蓝色", "(1,0)");
        editor.insertCharacter('L', 12, "绿色", "(2,0)");
        editor.insertCharacter('L', 12, "黄色", "(3,0)");  // L重复使用
        editor.insertCharacter('O', 12, "紫色", "(4,0)");

        // 显示享元池状态
        factory.showPoolStatus();   

        System.out.println("\n--- 场景2:继续输入文本 \"WORLD\" ---");
        // 输入 WORLD(部分字符已存在于享元池)
        editor.insertCharacter('W', 14, "橙色", "(5,0)");
        editor.insertCharacter('O', 14, "粉色", "(6,0)");  // O重复使用
        editor.insertCharacter('R', 14, "青色", "(7,0)");
        editor.insertCharacter('L', 14, "棕色", "(8,0)");  // L重复使用
        editor.insertCharacter('D', 14, "灰色", "(9,0)");

        // 显示享元池状态
        factory.showPoolStatus();

        // 显示文档内容
        editor.display();

        System.out.println("\n--- 场景3:输入长文本测试内存优化 ---");
        String longText = "HELLO WORLD! THIS IS A FLYWEIGHT PATTERN DEMO!";
        Editor editor2 = new Editor();
        
        System.out.println("正在输入: \"" + longText + "\"");
        System.out.println("文本长度: " + longText.length() + " 个字符\n");
        
        for (int i = 0; i < longText.length(); i++) {
            char c = longText.charAt(i);
            editor2.insertCharacter(c, 12, "黑色", "(" + i + ",1)");
        }
        
        // 显示享元池状态
        factory.showPoolStatus();
        
        // 显示内存优化效果
        System.out.println("\n📊 内存优化效果分析:");
        System.out.println("   文本总字符数: " + longText.length());
        System.out.println("   实际创建的对象数: " + factory.getPoolSize());
        System.out.println("   节省对象数: " + (longText.length() - factory.getPoolSize()));
        System.out.println("   内存节省率: " + String.format("%.1f%%", 
            (1 - (double)factory.getPoolSize() / longText.length()) * 100));

        System.out.println("\n--- 场景4:对比不使用享元模式 ---");
        System.out.println("\n❌ 不使用享元模式:");
        System.out.println("   每个字符都创建新对象");
        System.out.println("   " + longText.length() + " 个字符需要创建 " + longText.length() + " 个对象");
        System.out.println("   内存占用: " + longText.length() + " × 对象大小");
        
        System.out.println("\n✅ 使用享元模式:");
        System.out.println("   相同字符共享对象");
        System.out.println("   " + longText.length() + " 个字符只需要创建 " + factory.getPoolSize() + " 个对象");
        System.out.println("   内存占用: " + factory.getPoolSize() + " × 对象大小 + 外部状态");
        System.out.println("   大大节省了内存!");

        System.out.println("\n=== 享元模式说明 ===");
        System.out.println("1. 享元接口: CharacterFlyweight - 定义享元对象的接口");
        System.out.println("2. 具体享元: ConcreteCharacter - 实现享元接口,存储内部状态");
        System.out.println("3. 享元工厂: CharacterFactory - 管理享元对象池");
        System.out.println("4. 客户端: Editor - 维护外部状态,使用享元对象");
        System.out.println("5. 内部状态: 字符本身(可共享)");
        System.out.println("6. 外部状态: 字号、颜色、位置(不可共享)");

        System.out.println("\n=== 享元模式优势 ===");
        System.out.println("✅ 减少对象数量: 通过共享技术减少内存占用");
        System.out.println("✅ 提高性能: 减少对象创建和垃圾回收开销");
        System.out.println("✅ 适合大量相似对象: 特别适合大量细粒度对象");
        System.out.println("✅ 外部状态分离: 将可变部分外部化");

        System.out.println("\n=== 享元模式应用场景 ===");
        System.out.println("📌 文本编辑器: 字符对象共享");
        System.out.println("📌 游戏开发: 大量相同的粒子、子弹、树木等");
        System.out.println("📌 图形界面: 大量相同的图标、按钮等");
        System.out.println("📌 字符串常量池: Java String.intern()");
        System.out.println("📌 数据库连接池: 连接对象复用");
        System.out.println("📌 线程池: 线程对象复用");

        System.out.println("\n=== 享元模式关键点 ===");
        System.out.println("🔑 内部状态: 存储在享元对象内部,可以共享");
        System.out.println("🔑 外部状态: 随环境改变,不可共享,由客户端维护");
        System.out.println("🔑 享元工厂: 管理对象池,确保对象正确共享");
        System.out.println("🔑 单例工厂: 通常使用单例模式实现工厂");

        System.out.println("\n=== 享元模式注意事项 ===");
        System.out.println("⚠️ 线程安全: 多线程环境需要考虑同步问题");
        System.out.println("⚠️ 状态分离: 正确区分内部状态和外部状态");
        System.out.println("⚠️ 复杂度: 增加了系统复杂度,需权衡");
        System.out.println("⚠️ 适用条件: 只有大量相似对象时才有优势");
    }
}

Last updated

Was this helpful?