享元模式 (Flyweight Pattern)
运用共享技术有效地支持大量细粒度的对象
模式定义
享元模式是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度的对象。享元模式通过共享尽可能多的相似对象来减少内存使用和提高性能。它适用于系统中存在大量相似对象的场景,通过区分对象的内部状态和外部状态,将可以共享的部分抽取出来进行复用。
模式结构
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();
}
}
}
适用场景
- 系统中有大量相似对象
- 需要缓冲池的场景
- 对象状态可以分离
- 当系统中存在大量相似或相同的对象时,可以使用享元模式来减少对象数量,节省内存
- 当需要频繁创建和销毁对象,且对象状态可以外部化时,可以使用享元模式来实现对象缓冲池
- 当对象的内部状态和外部状态可以分离时,内部状态可以共享,外部状态可以由客户端传递
优缺点
优点
- 大大减少对象的创建,降低系统的内存占用
- 提高系统性能,特别是在需要创建大量细粒度对象的场景
- 外部状态可以根据环境变化而变化,内部状态可以共享
- 减少内存中对象的数量,节省系统资源
- 提高系统性能,减少对象创建和垃圾回收的开销
缺点
- 提高系统复杂性,需要分离内部状态和外部状态
- 外部状态具有固有化的倾向,不应该随着环境变化而变化
- 读取外部状态会使运行时间稍微变长
- 需要区分内部状态和外部状态,增加了程序的复杂性
- 外部状态的读取和管理会增加运行时间开销