模式定义

享元模式是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度的对象。享元模式通过共享尽可能多的相似对象来减少内存使用和提高性能。它适用于系统中存在大量相似对象的场景,通过区分对象的内部状态和外部状态,将可以共享的部分抽取出来进行复用。

模式结构

graph TD A[Client] --> B((Flyweight)) B --> C[ConcreteFlyweight] B --> D[UnsharedConcreteFlyweight] A --> E[FlyweightFactory] E --> B

实现方式

基础实现

定义享元接口,实现具体享元类和享元工厂类。

// 享元接口
public interface Shape {
    void draw(int x, int y, String color);
}

// 具体享元类 - 圆形
import java.util.HashMap;
import java.util.Map;

public class Circle implements Shape {
    private String color;
    private static final Map circleMap = new HashMap<>();
    
    private Circle(String color) {
        this.color = color;
    }
    
    // 享元工厂方法
    public static Circle createCircle(String color) {
        Circle circle = circleMap.get(color);
        
        if (circle == null) {
            circle = new Circle(color);
            circleMap.put(color, circle);
            System.out.println("创建新的圆形颜色: " + color);
        }
        
        return circle;
    }
    
    @Override
    public void draw(int x, int y, String color) {
        System.out.println("绘制圆形: 颜色=" + color + ", x=" + x + ", y=" + y);
    }
}

// 更完整的享元模式实现
public interface Flyweight {
    void operation(String extrinsicState);
}

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

// 享元工厂类
import java.util.HashMap;
import java.util.Map;

public class FlyweightFactory {
    private static final Map flyweights = new HashMap<>();
    
    public static Flyweight getFlyweight(String intrinsicState) {
        Flyweight flyweight = flyweights.get(intrinsicState);
        
        if (flyweight == null) {
            flyweight = new ConcreteFlyweight(intrinsicState);
            flyweights.put(intrinsicState, flyweight);
            System.out.println("创建新的享元对象: " + intrinsicState);
        }
        
        return flyweight;
    }
    
    public static int getFlyweightCount() {
        return flyweights.size();
    }
}

使用示例

public class FlyweightPatternDemo {
    private static final String[] colors = {"红色", "绿色", "蓝色", "黄色", "粉色"};
    
    public static void main(String[] args) {
        // 使用享元模式绘制多个圆形
        for (int i = 0; i < 20; i++) {
            Circle circle = Circle.createCircle(getRandomColor());
            circle.draw(getRandomX(), getRandomY(), circle.getClass().getSimpleName());
        }
        
        System.out.println("\n--- 使用标准享元模式 ---\n");
        
        // 使用标准享元模式
        for (int i = 0; i < 10; i++) {
            Flyweight flyweight = FlyweightFactory.getFlyweight(getRandomColor());
            flyweight.operation("外部状态 #" + i);
        }
        
        System.out.println("享元对象总数: " + FlyweightFactory.getFlyweightCount());
    }
    
    private static String getRandomColor() {
        return colors[(int) (Math.random() * colors.length)];
    }
    
    private static int getRandomX() {
        return (int) (Math.random() * 100);
    }
    
    private static int getRandomY() {
        return (int) (Math.random() * 100);
    }
}

文本编辑器示例

使用享元模式实现文本编辑器中的字符对象。

// 字符享元接口
public interface Character {
    void display(int fontSize, int x, int y);
}

// 具体字符享元类
public class CharacterFlyweight implements Character {
    private char symbol;
    private String fontFamily;
    private int fontWeight;
    
    public CharacterFlyweight(char symbol, String fontFamily, int fontWeight) {
        this.symbol = symbol;
        this.fontFamily = fontFamily;
        this.fontWeight = fontWeight;
    }
    
    @Override
    public void display(int fontSize, int x, int y) {
        System.out.println("字符: " + symbol + 
                          ", 字体: " + fontFamily + 
                          ", 粗细: " + fontWeight + 
                          ", 大小: " + fontSize + 
                          ", 位置: (" + x + "," + y + ")");
    }
}

// 字符享元工厂
import java.util.HashMap;
import java.util.Map;

public class CharacterFactory {
    private static final Map characters = new HashMap<>();
    
    public static CharacterFlyweight getCharacter(char symbol, String fontFamily, int fontWeight) {
        String key = symbol + ":" + fontFamily + ":" + fontWeight;
        CharacterFlyweight character = characters.get(key);
        
        if (character == null) {
            character = new CharacterFlyweight(symbol, fontFamily, fontWeight);
            characters.put(key, character);
            System.out.println("创建新字符: " + key);
        }
        
        return character;
    }
    
    public static int getCharacterCount() {
        return characters.size();
    }
}

// 文档类
import java.util.ArrayList;
import java.util.List;

public class Document {
    private List characters = new ArrayList<>();
    
    // 内部类,保存字符及其位置信息
    private static class CharacterPosition {
        CharacterFlyweight character;
        int fontSize;
        int x, y;
        
        CharacterPosition(CharacterFlyweight character, int fontSize, int x, int y) {
            this.character = character;
            this.fontSize = fontSize;
            this.x = x;
            this.y = y;
        }
    }
    
    public void addCharacter(char symbol, String fontFamily, int fontWeight, int fontSize, int x, int y) {
        CharacterFlyweight character = CharacterFactory.getCharacter(symbol, fontFamily, fontWeight);
        characters.add(new CharacterPosition(character, fontSize, x, y));
    }
    
    public void display() {
        for (CharacterPosition cp : characters) {
            cp.character.display(cp.fontSize, cp.x, cp.y);
        }
    }
}

// 客户端使用
public class TextEditor {
    public static void main(String[] args) {
        Document document = new Document();
        
        // 添加大量字符,相同字符属性会被共享
        document.addCharacter('H', "Arial", 400, 12, 0, 0);
        document.addCharacter('e', "Arial", 400, 12, 10, 0);
        document.addCharacter('l', "Arial", 400, 12, 20, 0);
        document.addCharacter('l', "Arial", 400, 12, 30, 0);
        document.addCharacter('o', "Arial", 400, 12, 40, 0);
        
        // 相同属性的字符会共享对象
        document.addCharacter('W', "Arial", 700, 12, 60, 0);
        document.addCharacter('o', "Arial", 400, 12, 70, 0);
        document.addCharacter('r', "Arial", 400, 12, 80, 0);
        document.addCharacter('l', "Arial", 400, 12, 90, 0);
        document.addCharacter('d', "Arial", 400, 12, 100, 0);
        
        // 不同字体属性会创建新对象
        document.addCharacter('!', "Times New Roman", 400, 14, 110, 0);
        
        System.out.println("文档内容:");
        document.display();
        
        System.out.println("\n实际创建的字符对象数: " + CharacterFactory.getCharacterCount());
    }
}

经典案例

Java String 常量池

Java 中的 String 常量池是享元模式的经典应用。

public class StringFlyweightExample {
    public static void main(String[] args) {
        // 字符串字面量会进入常量池,相同内容的字符串会共享同一个对象
        String str1 = "Hello";
        String str2 = "Hello";
        String str3 = "Hello";
        
        System.out.println("str1 == str2: " + (str1 == str2)); // true
        System.out.println("str2 == str3: " + (str2 == str3)); // true
        System.out.println("str1 == str3: " + (str1 == str3)); // true
        
        // 通过 new 创建的字符串不会进入常量池
        String str4 = new String("Hello");
        System.out.println("str1 == str4: " + (str1 == str4)); // false
        
        // 使用 intern() 方法可以将字符串放入常量池
        String str5 = str4.intern();
        System.out.println("str1 == str5: " + (str1 == str5)); // true
        
        System.out.println("\n--- Integer 缓冲池示例 ---\n");
        
        // Integer 也有类似的缓冲池机制 (-128 到 127)
        Integer int1 = 100;
        Integer int2 = 100;
        Integer int3 = 200;
        Integer int4 = 200;
        
        System.out.println("int1 == int2: " + (int1 == int2)); // true
        System.out.println("int3 == int4: " + (int3 == int4)); // false (超出缓冲范围)
        
        // 手动装箱会创建新对象
        Integer int5 = Integer.valueOf(100);
        Integer int6 = Integer.valueOf(100);
        System.out.println("int5 == int6: " + (int5 == int6)); // true
        
        Integer int7 = new Integer(100);
        Integer int8 = new Integer(100);
        System.out.println("int7 == int8: " + (int7 == int8)); // false
    }
}

游戏中的粒子系统

在游戏开发中,粒子系统经常使用享元模式来优化性能。

// 粒子类型享元接口
public interface ParticleType {
    void render(double x, double y, double rotation);
}

// 具体粒子类型享元类
public class ParticleFlyweight implements ParticleType {
    private String texture;
    private String color;
    private double size;
    
    public ParticleFlyweight(String texture, String color, double size) {
        this.texture = texture;
        this.color = color;
        this.size = size;
    }
    
    @Override
    public void render(double x, double y, double rotation) {
        System.out.println("渲染粒子: 纹理=" + texture + 
                          ", 颜色=" + color + 
                          ", 大小=" + size + 
                          ", 位置=(" + x + "," + y + ")" + 
                          ", 旋转=" + rotation);
    }
}

// 粒子享元工厂
import java.util.HashMap;
import java.util.Map;

public class ParticleFactory {
    private static final Map particleTypes = new HashMap<>();
    
    public static ParticleFlyweight getParticleType(String texture, String color, double size) {
        String key = texture + ":" + color + ":" + size;
        ParticleFlyweight particleType = particleTypes.get(key);
        
        if (particleType == null) {
            particleType = new ParticleFlyweight(texture, color, size);
            particleTypes.put(key, particleType);
            System.out.println("创建新的粒子类型: " + key);
        }
        
        return particleType;
    }
    
    public static int getParticleTypeCount() {
        return particleTypes.size();
    }
}

// 粒子实例类(包含外部状态)
public class Particle {
    private ParticleFlyweight particleType;
    private double x, y;
    private double rotation;
    
    public Particle(ParticleFlyweight particleType, double x, double y, double rotation) {
        this.particleType = particleType;
        this.x = x;
        this.y = y;
        this.rotation = rotation;
    }
    
    public void render() {
        particleType.render(x, y, rotation);
    }
    
    // 设置外部状态
    public void setPosition(double x, double y) {
        this.x = x;
        this.y = y;
    }
    
    public void setRotation(double rotation) {
        this.rotation = rotation;
    }
}

// 客户端使用
public class ParticleSystem {
    public static void main(String[] args) {
        // 创建粒子系统
        java.util.List particles = new java.util.ArrayList<>();
        
        // 创建不同类型的粒子
        ParticleFlyweight fireParticle = ParticleFactory.getParticleType("fire.png", "red", 5.0);
        ParticleFlyweight smokeParticle = ParticleFactory.getParticleType("smoke.png", "gray", 3.0);
        ParticleFlyweight sparkParticle = ParticleFactory.getParticleType("spark.png", "yellow", 1.0);
        
        // 创建大量粒子实例
        for (int i = 0; i < 100; i++) {
            Particle particle;
            double x = Math.random() * 800;
            double y = Math.random() * 600;
            double rotation = Math.random() * 360;
            
            // 随机选择粒子类型
            int type = (int) (Math.random() * 3);
            switch (type) {
                case 0:
                    particle = new Particle(fireParticle, x, y, rotation);
                    break;
                case 1:
                    particle = new Particle(smokeParticle, x, y, rotation);
                    break;
                default:
                    particle = new Particle(sparkParticle, x, y, rotation);
                    break;
            }
            
            particles.add(particle);
        }
        
        System.out.println("创建的粒子类型数: " + ParticleFactory.getParticleTypeCount());
        System.out.println("创建的粒子实例数: " + particles.size());
        
        // 渲染所有粒子
        System.out.println("\n渲染粒子:");
        for (Particle particle : particles) {
            particle.render();
        }
    }
}

适用场景

优缺点

优点

  • 大大减少对象的创建,降低系统的内存占用
  • 提高系统性能,特别是在需要创建大量细粒度对象的场景
  • 外部状态可以根据环境变化而变化,内部状态可以共享
  • 减少内存中对象的数量,节省系统资源
  • 提高系统性能,减少对象创建和垃圾回收的开销

缺点

  • 提高系统复杂性,需要分离内部状态和外部状态
  • 外部状态具有固有化的倾向,不应该随着环境变化而变化
  • 读取外部状态会使运行时间稍微变长
  • 需要区分内部状态和外部状态,增加了程序的复杂性
  • 外部状态的读取和管理会增加运行时间开销