结构型模式:享元模式

什么是享元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术有效地支持大量细粒度对象的复用。享元模式的核心思想是:当系统中存在大量相似对象时,将这些对象的内部状态共享,仅区分它们的外部状态,从而减少内存占用,提高系统性能。

享元这个术语源自拳击比赛中的"蝇量级"(最轻的级别),暗示这些共享对象非常轻量,以至于可以创建大量实例而不会导致性能问题。

享元模式解决的问题

享元模式主要解决以下问题:

  1. 内存使用效率问题:当需要创建大量相似对象时,传统方法会占用大量内存
  2. 对象创建开销问题:创建大量对象会带来显著的性能开销
  3. 状态管理复杂性:当对象数量庞大时,状态管理变得困难

享元模式的结构

享元模式包含以下几个核心角色:

  1. 享元接口(Flyweight):定义享元对象的接口,通过这个接口享元可以接受并作用于外部状态
  2. 具体享元(Concrete Flyweight):实现享元接口,存储内部状态,必须是可共享的
  3. 非共享具体享元(Unshared Concrete Flyweight):不需要共享的享元实现
  4. 享元工厂(Flyweight Factory):创建并管理享元对象,确保享元对象被正确地共享
  5. 客户端(Client):维护对享元的引用,计算或存储享元的外部状态

下面是享元模式的UML类图:

«interface»
Flyweight
+operation(extrinsicState)
ConcreteFlyweight
-intrinsicState
+operation(extrinsicState)
UnsharedConcreteFlyweight
-allState
+operation(extrinsicState)
FlyweightFactory
-flyweights: Map
+getFlyweight(key)
Client
-flyweight: Flyweight
-extrinsicState

内部状态与外部状态

在享元模式中,对象的状态被分为两种:

  1. 内部状态(Intrinsic State)

    • 存储在享元对象内部
    • 不会随环境改变而改变
    • 可以共享的部分
    • 通常在享元对象创建时设置,之后不再修改
  2. 外部状态(Extrinsic State)

    • 依赖于和随环境改变而改变的状态
    • 不可以共享的部分
    • 由客户端保存,在调用享元对象的方法时传递给享元
    • 每次调用可能不同

区分内部状态和外部状态是享元模式的关键:内部状态使对象可共享,外部状态使共享对象在不同场景下可以有不同表现。

享元模式的基本实现

下面是享元模式的基本Java实现:

// 享元接口
interface Flyweight {
    void operation(String extrinsicState);
}

// 具体享元类
class ConcreteFlyweight implements Flyweight {
    private final String intrinsicState; // 内部状态
    
    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
        System.out.println("创建具体享元对象: " + this.intrinsicState);
    }
    
    @Override
    public void operation(String extrinsicState) {
        System.out.println("具体享元" + intrinsicState + "执行操作,外部状态: " + extrinsicState);
    }
}

// 非共享具体享元类
class UnsharedConcreteFlyweight implements Flyweight {
    private final String allState; // 所有状态(没有区分内部和外部)
    
    public UnsharedConcreteFlyweight(String allState) {
        this.allState = allState;
        System.out.println("创建非共享具体享元对象: " + this.allState);
    }
    
    @Override
    public void operation(String extrinsicState) {
        System.out.println("非共享具体享元" + allState + "执行操作,参数: " + extrinsicState);
    }
}

// 享元工厂
class FlyweightFactory {
    private final Map<String, Flyweight> flyweights = new HashMap<>();
    
    // 获取享元对象
    public Flyweight getFlyweight(String key) {
        // 如果对象存在,则直接返回
        if (flyweights.containsKey(key)) {
            System.out.println("复用已有享元: " + key);
            return flyweights.get(key);
        }
        
        // 如果对象不存在,则创建一个新的享元对象
        Flyweight flyweight = new ConcreteFlyweight(key);
        flyweights.put(key, flyweight);
        return flyweight;
    }
    
    // 获取享元池中对象数量
    public int getFlyweightCount() {
        return flyweights.size();
    }
}

// 客户端
public class FlyweightExample {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
        
        // 获取并使用共享享元对象
        Flyweight flyweight1 = factory.getFlyweight("A");
        flyweight1.operation("First Call");
        
        Flyweight flyweight2 = factory.getFlyweight("B");
        flyweight2.operation("Second Call");
        
        Flyweight flyweight3 = factory.getFlyweight("A"); // 重复获取"A",将复用对象
        flyweight3.operation("Third Call");
        
        // 创建并使用非共享享元对象
        Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("X");
        unsharedFlyweight.operation("Unshared Call");
        
        // 查看共享池中的对象数量
        System.out.println("享元池中的对象数量: " + factory.getFlyweightCount());
        
        // 验证对象共享
        System.out.println("flyweight1 == flyweight3: " + (flyweight1 == flyweight3));
        System.out.println("flyweight1 == flyweight2: " + (flyweight1 == flyweight2));
    }
}

实际应用示例:字符渲染系统

下面通过一个字符渲染系统的例子来展示享元模式的实际应用:

import java.awt.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

// 字符享元接口
interface CharacterFlyweight {
    void display(int fontSize, Point position, Color color);
}

// 具体字符享元类
class ConcreteCharacter implements CharacterFlyweight {
    private final char symbol; // 内部状态 - 字符符号
    private final Font baseFont; // 内部状态 - 基本字体
    
    public ConcreteCharacter(char symbol, String fontFamily) {
        this.symbol = symbol;
        this.baseFont = new Font(fontFamily, Font.PLAIN, 10); // 基础大小为10
        System.out.println("创建字符: '" + symbol + "' 使用字体: " + fontFamily);
    }
    
    @Override
    public void display(int fontSize, Point position, Color color) {
        // 外部状态:字体大小、位置和颜色
        System.out.printf("显示字符: '%c' [字体: %s, 大小: %d, 位置: (%d,%d), 颜色: %s]%n", 
                         symbol, baseFont.getFamily(), fontSize, 
                         position.x, position.y, colorToHex(color));
    }
    
    private String colorToHex(Color color) {
        return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
    }
}

// 字符工厂 - 享元工厂
class CharacterFactory {
    private final Map<Character, CharacterFlyweight> characters = new HashMap<>();
    private final String[] fontFamilies = {"Arial", "Times New Roman", "Courier New"};
    
    public CharacterFlyweight getCharacter(char symbol) {
        // 检查该字符是否已存在
        if (characters.containsKey(symbol)) {
            return characters.get(symbol);
        }
        
        // 为不同的字符选择不同的字体(仅作演示)
        String fontFamily = fontFamilies[Math.abs(symbol) % fontFamilies.length];
        
        // 创建新的字符享元对象
        CharacterFlyweight character = new ConcreteCharacter(symbol, fontFamily);
        characters.put(symbol, character);
        return character;
    }
    
    public int getCharacterCount() {
        return characters.size();
    }
}

// 文本区域 - 管理一组字符
class TextArea {
    private final CharacterFactory factory;
    private final Map<Point, CharData> characterData = new HashMap<>();
    
    public TextArea(CharacterFactory factory) {
        this.factory = factory;
    }
    
    // 添加一个字符到文本区域
    public void addCharacterAt(char c, int fontSize, Point position, Color color) {
        characterData.put(position, new CharData(c, fontSize, color));
    }
    
    // 渲染文本
    public void render() {
        System.out.println("\n===== 渲染文本区域 =====");
        
        for (Map.Entry<Point, CharData> entry : characterData.entrySet()) {
            Point position = entry.getKey();
            CharData data = entry.getValue();
            
            // 获取享元对象并调用其方法
            CharacterFlyweight character = factory.getCharacter(data.symbol);
            character.display(data.fontSize, position, data.color);
        }
    }
    
    // 内部类,存储字符的外部状态
    private static class CharData {
        char symbol;
        int fontSize;
        Color color;
        
        CharData(char symbol, int fontSize, Color color) {
            this.symbol = symbol;
            this.fontSize = fontSize;
            this.color = color;
        }
    }
}

// 字符渲染系统示例
public class CharacterRenderingSystem {
    public static void main(String[] args) {
        CharacterFactory factory = new CharacterFactory();
        TextArea textArea = new TextArea(factory);
        Random random = new Random();
        
        // 创建一些随机文本
        String text = "Hello, Flyweight Pattern! This is a demonstration of memory efficiency.";
        int x = 10, y = 10;
        
        for (char c : text.toCharArray()) {
            // 为每个字符设置不同的位置、大小和颜色(外部状态)
            int fontSize = 12 + random.nextInt(10); // 12-21的字体大小
            Color color = new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256));
            Point position = new Point(x, y);
            
            textArea.addCharacterAt(c, fontSize, position, color);
            
            x += fontSize / 2; // 水平位置根据字体大小调整
            if (x > 500) { // 换行
                x = 10;
                y += 25;
            }
        }
        
        // 渲染文本
        textArea.render();
        
        // 显示创建的字符数量
        System.out.println("\n创建的唯一字符数量: " + factory.getCharacterCount());
        System.out.println("文本总长度: " + text.length() + " 字符");
        System.out.println("节省的对象数量: " + (text.length() - factory.getCharacterCount()));
    }
}

实际应用示例:游戏中的粒子系统

下面通过一个游戏粒子系统的例子进一步说明享元模式的应用:

import java.awt.*;
import java.util.*;
import java.util.List;

// 粒子类型(享元接口)
interface ParticleType {
    void render(Point position, Color color, float size, float rotation, float alpha);
    String getTextureName();
}

// 具体粒子类型(具体享元)
class ConcreteParticleType implements ParticleType {
    private final String textureName; // 内部状态 - 纹理名称
    private final boolean isAdditive; // 内部状态 - 是否加法混合
    private final int drawPriority;   // 内部状态 - 绘制优先级
    
    public ConcreteParticleType(String textureName, boolean isAdditive, int drawPriority) {
        this.textureName = textureName;
        this.isAdditive = isAdditive;
        this.drawPriority = drawPriority;
        System.out.println("创建粒子类型: " + textureName + 
                         (isAdditive ? " (加法混合)" : " (普通混合)") + 
                         " 优先级: " + drawPriority);
    }
    
    @Override
    public void render(Point position, Color color, float size, float rotation, float alpha) {
        // 外部状态:位置、颜色、大小、旋转和透明度
        System.out.printf("渲染粒子 '%s' [位置: (%d,%d), 颜色: %s, 大小: %.1f, 旋转: %.1f°, 透明度: %.2f]%n",
                        textureName, position.x, position.y, colorToHex(color), 
                        size, rotation, alpha);
    }
    
    @Override
    public String getTextureName() {
        return textureName;
    }
    
    private String colorToHex(Color color) {
        return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
    }
}

// 粒子类型工厂(享元工厂)
class ParticleTypeFactory {
    private final Map<String, ParticleType> particleTypes = new HashMap<>();
    
    public ParticleType getParticleType(String textureName, boolean isAdditive, int drawPriority) {
        // 创建一个复合键
        String key = textureName + "|" + isAdditive + "|" + drawPriority;
        
        // 如果该粒子类型已存在,则直接返回
        if (particleTypes.containsKey(key)) {
            return particleTypes.get(key);
        }
        
        // 创建新的粒子类型
        ParticleType type = new ConcreteParticleType(textureName, isAdditive, drawPriority);
        particleTypes.put(key, type);
        return type;
    }
    
    public int getTypeCount() {
        return particleTypes.size();
    }
}

// 粒子实例 - 包含外部状态
class Particle {
    private ParticleType type;  // 享元引用
    private Point position;     // 外部状态 - 位置
    private Color color;        // 外部状态 - 颜色
    private float size;         // 外部状态 - 大小
    private float rotation;     // 外部状态 - 旋转
    private float alpha;        // 外部状态 - 透明度
    private float velocityX;    // 外部状态 - X轴速度
    private float velocityY;    // 外部状态 - Y轴速度
    private float lifespan;     // 外部状态 - 生命周期
    private float age;          // 外部状态 - 当前年龄
    
    public Particle(ParticleType type, Point position, Color color, 
                   float size, float rotation, float alpha,
                   float velocityX, float velocityY, float lifespan) {
        this.type = type;
        this.position = position;
        this.color = color;
        this.size = size;
        this.rotation = rotation;
        this.alpha = alpha;
        this.velocityX = velocityX;
        this.velocityY = velocityY;
        this.lifespan = lifespan;
        this.age = 0;
    }
    
    // 更新粒子状态
    public boolean update(float deltaTime) {
        // 更新位置
        position.x += velocityX * deltaTime;
        position.y += velocityY * deltaTime;
        
        // 更新年龄
        age += deltaTime;
        
        // 基于年龄更新透明度
        alpha = 1.0f - (age / lifespan);
        
        // 更新旋转
        rotation += 45 * deltaTime; // 每秒旋转45度
        
        // 检查粒子是否存活
        return age < lifespan;
    }
    
    // 渲染粒子
    public void render() {
        type.render(position, color, size, rotation, alpha);
    }
    
    // 获取粒子类型
    public ParticleType getType() {
        return type;
    }
}

// 粒子发射器 - 创建和管理粒子
class ParticleEmitter {
    private final ParticleTypeFactory typeFactory;
    private final List<Particle> activeParticles = new ArrayList<>();
    private final Random random = new Random();
    private final Point position;
    private final String[] textureNames = {"smoke", "fire", "spark", "dust"};
    
    public ParticleEmitter(ParticleTypeFactory typeFactory, Point position) {
        this.typeFactory = typeFactory;
        this.position = position;
    }
    
    // 发射新粒子
    public void emit(int count) {
        for (int i = 0; i < count; i++) {
            // 随机选择粒子类型
            String textureName = textureNames[random.nextInt(textureNames.length)];
            boolean isAdditive = textureName.equals("fire") || textureName.equals("spark");
            int drawPriority = isAdditive ? 2 : 1;
            
            // 从工厂获取粒子类型
            ParticleType type = typeFactory.getParticleType(textureName, isAdditive, drawPriority);
            
            // 随机生成外部状态
            Point particlePos = new Point(
                position.x + random.nextInt(21) - 10,
                position.y + random.nextInt(21) - 10
            );
            
            // 为不同类型的粒子设置不同的颜色
            Color color;
            switch (textureName) {
                case "fire":
                    color = new Color(255, 100 + random.nextInt(156), 0);
                    break;
                case "spark":
                    color = new Color(255, 200 + random.nextInt(56), 0);
                    break;
                case "smoke":
                    int gray = 150 + random.nextInt(106);
                    color = new Color(gray, gray, gray);
                    break;
                default:
                    color = new Color(
                        150 + random.nextInt(106),
                        150 + random.nextInt(106),
                        150 + random.nextInt(106)
                    );
            }
            
            float size = 5 + random.nextFloat() * 15;
            float rotation = random.nextFloat() * 360;
            float alpha = 0.7f + random.nextFloat() * 0.3f;
            float velocityX = -5 + random.nextFloat() * 10;
            float velocityY = -2 - random.nextFloat() * 3; // 向上移动
            float lifespan = 1 + random.nextFloat() * 3; // 1-4秒生命周期
            
            // 创建新粒子
            Particle particle = new Particle(type, particlePos, color, size, rotation, alpha,
                                          velocityX, velocityY, lifespan);
            activeParticles.add(particle);
        }
    }
    
    // 更新所有粒子
    public void update(float deltaTime) {
        Iterator<Particle> iterator = activeParticles.iterator();
        while (iterator.hasNext()) {
            Particle particle = iterator.next();
            boolean isAlive = particle.update(deltaTime);
            
            if (!isAlive) {
                iterator.remove(); // 移除死亡的粒子
            }
        }
    }
    
    // 渲染所有粒子
    public void render() {
        System.out.println("\n===== 渲染粒子系统 (" + activeParticles.size() + " 个活跃粒子) =====");
        for (Particle particle : activeParticles) {
            particle.render();
        }
    }
    
    // 获取活跃粒子数量
    public int getActiveParticleCount() {
        return activeParticles.size();
    }
}

// 粒子系统示例
public class ParticleSystemExample {
    public static void main(String[] args) {
        ParticleTypeFactory typeFactory = new ParticleTypeFactory();
        ParticleEmitter emitter = new ParticleEmitter(typeFactory, new Point(300, 300));
        
        // 模拟游戏循环
        float deltaTime = 0.1f; // 模拟每帧0.1秒
        
        System.out.println("===== 开始粒子系统模拟 =====\n");
        
        // 第1帧:发射一些粒子
        System.out.println("帧 1: 初始发射粒子");
        emitter.emit(20);
        emitter.update(deltaTime);
        emitter.render();
        
        // 第5帧:发射更多粒子并更新现有粒子
        System.out.println("\n帧 5: 发射更多粒子");
        emitter.emit(15);
        for (int i = 0; i < 4; i++) {
            emitter.update(deltaTime);
        }
        emitter.render();
        
        // 第10帧:最后一次发射并更新
        System.out.println("\n帧 10: 最后一次发射");
        emitter.emit(10);
        for (int i = 0; i < 5; i++) {
            emitter.update(deltaTime);
        }
        emitter.render();
        
        // 第20帧:只更新现有粒子
        System.out.println("\n帧 20: 更新现有粒子");
        for (int i = 0; i < 10; i++) {
            emitter.update(deltaTime);
        }
        emitter.render();
        
        // 显示统计信息
        System.out.println("\n===== 粒子系统统计 =====");
        System.out.println("创建的粒子类型数量: " + typeFactory.getTypeCount());
        System.out.println("当前活跃粒子数量: " + emitter.getActiveParticleCount());
        System.out.println("节省的内存对象数量: " + 
                         (emitter.getActiveParticleCount() - typeFactory.getTypeCount()));
    }
}

享元模式在Java API中的应用

Java API中有多个享元模式的应用:

  1. Java基本数据类型的包装类:如IntegerBooleanCharacter等。例如,Integer.valueOf()方法会缓存-128到127之间的对象:
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b); // 输出true,因为共享了同一个对象

Integer c = Integer.valueOf(1000);
Integer d = Integer.valueOf(1000);
System.out.println(c == d); // 输出false,超出了缓存范围
  1. 字符串常量池:Java的字符串字面量会被存储在字符串常量池中共享:
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // 输出true,s1和s2引用同一个字符串对象
  1. javax.swing中的边框和颜色对象:Swing中的BorderFactory创建的边框对象和系统颜色对象都使用了享元模式。

享元模式的适用场景

享元模式在以下情况特别有用:

  1. 系统中存在大量相似对象:当系统中有大量相似对象,导致内存占用过高时
  2. 对象的状态可以分为内部状态和外部状态:部分状态可以共享,部分需要独立存储
  3. 创建对象的成本很高:对象的创建涉及复杂的计算或资源加载
  4. 程序需要缓冲池:需要维护一组可复用的对象
  5. 特定领域的应用:文本编辑器的字符渲染、游戏中的粒子系统、CAD系统中的图形元素等

享元模式与其他模式的比较

享元模式 vs 单例模式

  • 享元模式:管理多个共享对象,每个对象有自己的内部状态
  • 单例模式:确保一个类只有一个实例

享元模式 vs 对象池模式

  • 享元模式:强调对象共享,对象在其整个生命周期内不会被销毁
  • 对象池模式:强调对象复用,对象使用完成后放回池中供下次使用

享元模式 vs 缓存

  • 享元模式:是一种设计模式,专注于减少对象数量和内存占用
  • 缓存:是一种通用技术,关注数据访问性能,不一定涉及对象共享

享元模式 vs 组合模式

  • 享元模式:关注对象共享和内存优化
  • 组合模式:关注对象的树形结构组织

享元模式的优点

  1. 大幅减少内存使用:通过共享相同的对象状态,显著减少内存占用
  2. 提高性能:减少对象创建和垃圾回收的开销
  3. 集中状态管理:内部状态集中管理,易于维护
  4. 提高复杂系统的可行性:使得原本因为对象数量庞大而不可行的系统变得可行

享元模式的缺点

  1. 增加系统复杂性:区分内部状态和外部状态增加了设计复杂性
  2. 运行时间成本:将外部状态传递给享元对象可能增加运行时间开销
  3. 线程安全问题:对共享对象的并发访问需要考虑线程安全
  4. 调试困难:由于对象共享,调试可能更加困难

享元模式的最佳实践

  1. 正确区分内外部状态:确保内部状态可共享,外部状态由客户端管理
  2. 使用工厂管理享元对象:确保享元对象正确共享
  3. 考虑对象的不变性:享元对象最好是不可变的,确保线程安全
  4. 控制享元粒度:选择合适的粒度级别,不要过度细化导致管理复杂化
  5. 限制享元数量:考虑内存与性能的平衡,可能需要清理策略
  6. 考虑初始化成本:可以使用延迟加载或按需创建享元对象
  7. 注意享元的生命周期:享元通常和应用程序具有相同的生命周期

享元模式的性能考量

享元模式主要的性能考量包括:

  1. 内存vs时间权衡:内存节省是以时间成本为代价的,需要在两者之间找到平衡
  2. 对象创建成本:如果对象创建成本高,享元模式可以显著提高性能
  3. 外部状态传递开销:传递外部状态的开销可能影响性能
  4. 缓存局部性:频繁访问的对象应该保持良好的缓存局部性
  5. 同步开销:在多线程环境中,对享元工厂的同步可能带来额外开销

并发环境下的享元模式

在多线程环境中使用享元模式需要特别注意:

// 线程安全的享元工厂
class ConcurrentFlyweightFactory {
    private final Map<String, Flyweight> flyweights = new ConcurrentHashMap<>();
    
    public Flyweight getFlyweight(String key) {
        // ConcurrentHashMap的操作是线程安全的
        return flyweights.computeIfAbsent(key, k -> new ConcreteFlyweight(k));
    }
}

或者使用双重检查锁定:

// 使用双重检查锁定的享元工厂
class ThreadSafeFlyweightFactory {
    private final Map<String, Flyweight> flyweights = new HashMap<>();
    
    public synchronized Flyweight getFlyweight(String key) {
        if (!flyweights.containsKey(key)) {
            flyweights.put(key, new ConcreteFlyweight(key));
        }
        return flyweights.get(key);
    }
}

高级应用:多层次享元

在某些复杂系统中,可以实现多层次的享元模式:

// 多层次享元示例

// 基本形状享元
interface ShapeFlyweight {
    void draw(Graphics g, Point position, Color color, float scale);
}

// 复合形状享元 - 由多个基本形状组成
class CompositeShape implements ShapeFlyweight {
    private final List<ShapeComponent> components = new ArrayList<>();
    
    // 添加基本形状
    public void addComponent(ShapeFlyweight flyweight, Point relativePosition) {
        components.add(new ShapeComponent(flyweight, relativePosition));
    }
    
    @Override
    public void draw(Graphics g, Point position, Color color, float scale) {
        for (ShapeComponent component : components) {
            Point componentPos = new Point(
                position.x + (int)(component.relativePosition.x * scale),
                position.y + (int)(component.relativePosition.y * scale)
            );
            component.flyweight.draw(g, componentPos, color, scale);
        }
    }
    
    // 内部类表示复合形状的组件
    private static class ShapeComponent {
        ShapeFlyweight flyweight;
        Point relativePosition;
        
        ShapeComponent(ShapeFlyweight flyweight, Point relativePosition) {
            this.flyweight = flyweight;
            this.relativePosition = relativePosition;
        }
    }
}

// 多层次享元工厂
class ShapeFactory {
    private final Map<String, ShapeFlyweight> basicShapes = new HashMap<>();
    private final Map<String, CompositeShape> compositeShapes = new HashMap<>();
    
    // 获取基本形状
    public ShapeFlyweight getBasicShape(String type) {
        return basicShapes.computeIfAbsent(type, this::createBasicShape);
    }
    
    // 获取复合形状
    public ShapeFlyweight getCompositeShape(String type) {
        return compositeShapes.computeIfAbsent(type, this::createCompositeShape);
    }
    
    // 创建基本形状
    private ShapeFlyweight createBasicShape(String type) {
        // 实现创建基本形状的逻辑
        // ...
    }
    
    // 创建复合形状
    private CompositeShape createCompositeShape(String type) {
        CompositeShape shape = new CompositeShape();
        
        // 根据类型创建不同的复合形状
        if ("tree".equals(type)) {
            // 添加树干
            shape.addComponent(getBasicShape("rectangle"), new Point(0, 20));
            // 添加树冠
            shape.addComponent(getBasicShape("circle"), new Point(0, 0));
            shape.addComponent(getBasicShape("circle"), new Point(-10, 10));
            shape.addComponent(getBasicShape("circle"), new Point(10, 10));
        } else if ("house".equals(type)) {
            // 添加房子的墙
            shape.addComponent(getBasicShape("square"), new Point(0, 10));
            // 添加房子的屋顶
            shape.addComponent(getBasicShape("triangle"), new Point(0, -10));
        }
        
        return shape;
    }
}

享元模式在现代编程中的应用

前端开发中的应用

在现代Web开发中,享元模式也有广泛应用:

// JavaScript中的享元模式示例
class Particle {
  constructor(texture) {
    this.texture = texture; // 内部状态
  }
  
  display(x, y, scale, rotation) {
    console.log(`显示纹理 ${this.texture} 在位置(${x},${y}),缩放: ${scale},旋转: ${rotation}`);
  }
}

class ParticleFactory {
  constructor() {
    this.particles = {};
  }
  
  getParticle(texture) {
    if (!this.particles[texture]) {
      this.particles[texture] = new Particle(texture);
      console.log(`创建新的粒子纹理: ${texture}`);
    }
    return this.particles[texture];
  }
}

// 使用享元模式
const factory = new ParticleFactory();
const particles = [];

// 创建1000个粒子,但只有5种不同的纹理
for (let i = 0; i < 1000; i++) {
  const textureIndex = i % 5;
  const texture = `texture${textureIndex}`;
  
  // 获取享元对象
  const particle = factory.getParticle(texture);
  
  // 存储外部状态
  particles.push({
    flyweight: particle,
    x: Math.random() * 500,
    y: Math.random() * 500,
    scale: 0.5 + Math.random(),
    rotation: Math.random() * 360
  });
}

// 渲染所有粒子
console.log("渲染1000个粒子:");
for (let i = 0; i < 10; i++) { // 为了简洁,只打印前10个
  const p = particles[i];
  p.flyweight.display(p.x, p.y, p.scale, p.rotation);
}

console.log(`创建的实际纹理对象数量: ${Object.keys(factory.particles).length}`);
console.log(`节省的对象数量: ${particles.length - Object.keys(factory.particles).length}`);

React中的享元模式

在React等现代前端框架中,享元模式的思想也有所体现:

// React中的享元理念
function ItemRenderer({ type, data }) {
  // 根据类型选择不同的渲染器
  const renderItem = (type) => {
    switch(type) {
      case 'text':
        return <TextItem />;
      case 'image':
        return <ImageItem />;
      case 'video':
        return <VideoItem />;
      default:
        return <DefaultItem />;
    }
  };
  
  const ItemComponent = renderItem(type);
  
  // 渲染组件并传入外部状态(data)
  return <ItemComponent data={data} />;
}

// 用法
function ItemList({ items }) {
  return (
    <div>
      {items.map(item => (
        <ItemRenderer 
          key={item.id}
          type={item.type}
          data={item}
        />
      ))}
    </div>
  );
}

享元模式在微服务架构中的应用

在微服务架构中,享元模式的思想也可以应用于连接池和服务实例管理:

// 微服务中的连接享元
class ServiceConnectionPool {
    private final Map<String, ServiceConnection> connections = new ConcurrentHashMap<>();
    private final ServiceConnectionFactory factory;
    
    public ServiceConnectionPool(ServiceConnectionFactory factory) {
        this.factory = factory;
    }
    
    public ServiceConnection getConnection(String serviceId) {
        return connections.computeIfAbsent(serviceId, id -> {
            System.out.println("创建到服务 " + id + " 的新连接");
            return factory.createConnection(id);
        });
    }
    
    public void closeConnection(String serviceId) {
        ServiceConnection connection = connections.remove(serviceId);
        if (connection != null) {
            connection.close();
        }
    }
    
    public int getActiveConnectionCount() {
        return connections.size();
    }
}

享元模式的变体

复合享元

复合享元模式允许将多个享元对象组合成一个更大的享元对象:

// 复合享元接口
interface CompositeFlyweight extends Flyweight {
    void add(String key, Flyweight flyweight);
    void remove(String key);
}

// 复合享元实现
class ConcreteCompositeFlyweight implements CompositeFlyweight {
    private final Map<String, Flyweight> children = new HashMap<>();
    
    @Override
    public void operation(String extrinsicState) {
        for (Flyweight flyweight : children.values()) {
            flyweight.operation(extrinsicState);
        }
    }
    
    @Override
    public void add(String key, Flyweight flyweight) {
        children.put(key, flyweight);
    }
    
    @Override
    public void remove(String key) {
        children.remove(key);
    }
}

// 支持复合享元的工厂
class ExtendedFlyweightFactory {
    private final Map<String, Flyweight> flyweights = new HashMap<>();
    
    // 获取单个享元
    public Flyweight getFlyweight(String key) {
        if (!flyweights.containsKey(key)) {
            flyweights.put(key, new ConcreteFlyweight(key));
        }
        return flyweights.get(key);
    }
    
    // 获取复合享元
    public Flyweight getCompositeFlyweight(String... keys) {
        CompositeFlyweight compositeFlyweight = new ConcreteCompositeFlyweight();
        
        for (String key : keys) {
            compositeFlyweight.add(key, getFlyweight(key));
        }
        
        return compositeFlyweight;
    }
}

参数化享元

参数化享元通过函数参数传递内部状态,而不是在享元对象中存储:

// 参数化享元接口
interface ParameterizedFlyweight {
    void operation(String intrinsicState, String extrinsicState);
}

// 参数化享元实现
class ConcreteParameterizedFlyweight implements ParameterizedFlyweight {
    @Override
    public void operation(String intrinsicState, String extrinsicState) {
        System.out.println("参数化享元操作 - 内部状态: " + intrinsicState + 
                         ", 外部状态: " + extrinsicState);
    }
}

// 参数化享元工厂
class ParameterizedFlyweightFactory {
    private final ParameterizedFlyweight flyweight = new ConcreteParameterizedFlyweight();
    
    public ParameterizedFlyweight getFlyweight() {
        return flyweight;
    }
}

享元模式与缓存策略

在实际应用中,享元池可能需要缓存管理策略,如LRU(最近最少使用)或LFU(最少使用频率):

// 带LRU缓存的享元工厂
class LRUFlyweightFactory<K, V> {
    private final int capacity;
    private final Map<K, V> cache = new LinkedHashMap<>();
    private final Function<K, V> creator;
    
    public LRUFlyweightFactory(int capacity, Function<K, V> creator) {
        this.capacity = capacity;
        this.creator = creator;
    }
    
    public synchronized V getFlyweight(K key) {
        if (cache.containsKey(key)) {
            // 移动到链表末尾(表示最近使用)
            V value = cache.remove(key);
            cache.put(key, value);
            return value;
        }
        
        // 创建新的享元
        V newValue = creator.apply(key);
        
        // 如果缓存已满,移除最久未使用的项
        if (cache.size() >= capacity) {
            K oldestKey = cache.keySet().iterator().next();
            cache.remove(oldestKey);
        }
        
        // 添加新创建的享元
        cache.put(key, newValue);
        return newValue;
    }
    
    public int getCacheSize() {
        return cache.size();
    }
}

享元模式与内存管理

享元模式对内存管理有重要影响,尤其是在处理大量对象的系统中:

// 内存敏感型享元工厂
class MemorySensitiveFlyweightFactory {
    private final Map<String, WeakReference<Flyweight>> flyweights = new HashMap<>();
    
    public Flyweight getFlyweight(String key) {
        WeakReference<Flyweight> ref = flyweights.get(key);
        Flyweight flyweight = (ref != null) ? ref.get() : null;
        
        if (flyweight == null) {
            flyweight = new ConcreteFlyweight(key);
            flyweights.put(key, new WeakReference<>(flyweight));
        }
        
        return flyweight;
    }
    
    // 清理失效的引用
    public void cleanUp() {
        flyweights.entrySet().removeIf(entry -> entry.getValue().get() == null);
    }
}

享元模式在数据库连接池中的应用

数据库连接池是享元模式的一个典型应用:

// 数据库连接享元
class DatabaseConnection {
    private final String url;
    private final String username;
    private final String password;
    private Connection connection;
    private boolean inUse;
    
    public DatabaseConnection(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.inUse = false;
    }
    
    public synchronized void open() throws SQLException {
        if (connection == null || connection.isClosed()) {
            connection = DriverManager.getConnection(url, username, password);
        }
    }
    
    public synchronized void close() {
        inUse = false;
    }
    
    public synchronized boolean isInUse() {
        return inUse;
    }
    
    public synchronized void setInUse(boolean inUse) {
        this.inUse = inUse;
    }
    
    public Connection getConnection() {
        return connection;
    }
}

// 数据库连接池
class DatabaseConnectionPool {
    private final List<DatabaseConnection> connections = new ArrayList<>();
    private final String url;
    private final String username;
    private final String password;
    private final int maxConnections;
    
    public DatabaseConnectionPool(String url, String username, String password, int maxConnections) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.maxConnections = maxConnections;
    }
    
    public synchronized DatabaseConnection getConnection() throws SQLException {
        // 查找空闲连接
        for (DatabaseConnection conn : connections) {
            if (!conn.isInUse()) {
                conn.setInUse(true);
                return conn;
            }
        }
        
        // 如果没有空闲连接但未达到最大连接数,创建新连接
        if (connections.size() < maxConnections) {
            DatabaseConnection conn = new DatabaseConnection(url, username, password);
            conn.open();
            conn.setInUse(true);
            connections.add(conn);
            return conn;
        }
        
        // 无法获取连接
        throw new SQLException("无法获取数据库连接:已达到最大连接数");
    }
    
    public synchronized void releaseConnection(DatabaseConnection conn) {
        conn.setInUse(false);
    }
    
    public synchronized int getActiveConnectionCount() {
        int count = 0;
        for (DatabaseConnection conn : connections) {
            if (conn.isInUse()) {
                count++;
            }
        }
        return count;
    }
    
    public synchronized int getTotalConnectionCount() {
        return connections.size();
    }
}

享元模式与对象池的关系

享元模式和对象池虽然相似,但有重要区别:

// 对象池示例
class ObjectPool<T> {
    private final List<T> available = new ArrayList<>();
    private final List<T> inUse = new ArrayList<>();
    private final Supplier<T> objectFactory;
    
    public ObjectPool(Supplier<T> objectFactory, int initialSize) {
        this.objectFactory = objectFactory;
        
        // 预创建对象
        for (int i = 0; i < initialSize; i++) {
            available.add(objectFactory.get());
        }
    }
    
    public synchronized T borrow() {
        if (available.isEmpty()) {
            // 没有可用对象,创建一个新的
            T newObject = objectFactory.get();
            inUse.add(newObject);
            return newObject;
        } else {
            // 使用现有对象
            T object = available.remove(available.size() - 1);
            inUse.add(object);
            return object;
        }
    }
    
    public synchronized void release(T object) {
        if (inUse.contains(object)) {
            inUse.remove(object);
            available.add(object);
        }
    }
    
    public synchronized int getAvailableCount() {
        return available.size();
    }
    
    public synchronized int getInUseCount() {
        return inUse.size();
    }
}

性能测试:享元模式的效果

下面是一个简单的性能测试,对比使用享元模式和不使用享元模式的内存和性能差异:

// 性能测试类
public class FlyweightPerformanceTest {
    // 不使用享元的类
    static class StandardObject {
        private String state;
        
        public StandardObject(String state) {
            this.state = state;
        }
        
        public void operation() {
            // 模拟操作
        }
    }
    
    // 使用享元的类
    static class FlyweightObject {
        private String intrinsicState;
        
        public FlyweightObject(String intrinsicState) {
            this.intrinsicState = intrinsicState;
        }
        
        public void operation(String extrinsicState) {
            // 模拟操作
        }
    }
    
    // 享元工厂
    static class FlyweightFactory {
        private Map<String, FlyweightObject> flyweights = new HashMap<>();
        
        public FlyweightObject getFlyweight(String key) {
            if (!flyweights.containsKey(key)) {
                flyweights.put(key, new FlyweightObject(key));
            }
            return flyweights.get(key);
        }
        
        public int getCount() {
            return flyweights.size();
        }
    }
    
    public static void main(String[] args) {
        final int OBJECT_COUNT = 1_000_000;
        final int DISTINCT_STATES = 100;
        
        System.out.println("开始性能测试 - 创建 " + OBJECT_COUNT + " 个对象...");
        
        // 测试不使用享元
        long startTimeStandard = System.currentTimeMillis();
        List<StandardObject> standardObjects = new ArrayList<>(OBJECT_COUNT);
        
        for (int i = 0; i < OBJECT_COUNT; i++) {
            String state = "State" + (i % DISTINCT_STATES);
            standardObjects.add(new StandardObject(state));
        }
        
        for (StandardObject obj : standardObjects) {
            obj.operation();
        }
        
        long endTimeStandard = System.currentTimeMillis();
        
        // 测试使用享元
        long startTimeFlyweight = System.currentTimeMillis();
        FlyweightFactory factory = new FlyweightFactory();
        List<String> extrinsicStates = new ArrayList<>(OBJECT_COUNT);
        
        for (int i = 0; i < OBJECT_COUNT; i++) {
            String intrinsicState = "State" + (i % DISTINCT_STATES);
            factory.getFlyweight(intrinsicState);
            extrinsicStates.add("ExtrinsicState" + i);
        }
        
        int index = 0;
        for (String state : extrinsicStates) {
            String intrinsicState = "State" + (index % DISTINCT_STATES);
            FlyweightObject flyweight = factory.getFlyweight(intrinsicState);
            flyweight.operation(state);
            index++;
        }
        
        long endTimeFlyweight = System.currentTimeMillis();
        
        // 输出结果
        System.out.println("\n===== 性能测试结果 =====");
        System.out.println("标准对象:");
        System.out.println("  - 创建对象数量: " + standardObjects.size());
        System.out.println("  - 执行时间: " + (endTimeStandard - startTimeStandard) + "ms");
        
        System.out.println("\n享元对象:");
        System.out.println("  - 创建享元对象数量: " + factory.getCount());
        System.out.println("  - 处理对象数量: " + extrinsicStates.size());
        System.out.println("  - 执行时间: " + (endTimeFlyweight - startTimeFlyweight) + "ms");
        
        System.out.println("\n内存节省: " + (OBJECT_COUNT - factory.getCount()) + 
                         " 个对象 (" + (100.0 * (OBJECT_COUNT - factory.getCount()) / OBJECT_COUNT) + "%)");
    }
}

总结

享元模式是一种优化内存使用的结构型设计模式,它通过共享相似对象的内部状态来减少内存占用,提高系统性能。当系统中存在大量相似对象,这些对象的状态可以分为内部状态和外部状态时,享元模式特别有用。

享元模式的核心概念包括:

  1. 区分内部状态和外部状态
  2. 通过享元工厂管理共享对象
  3. 客户端负责管理外部状态

在实际应用中,享元模式广泛应用于文本编辑器、图形系统、游戏开发、连接池管理等场景。Java API中的包装类、字符串常量池等也使用了享元模式的思想。

虽然享元模式可以显著减少内存使用并提高性能,但它也增加了系统的复杂性,并可能引入并发问题。因此,在使用享元模式时,需要权衡内存节省与系统复杂性之间的关系,确保正确区分内部状态和外部状态,并妥善处理享元对象的生命周期。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Luck_ff0810

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值