文章目录
1. 什么是享元模式?
享元模式是一种结构型设计模式,它通过共享技术有效地支持大量细粒度的对象。享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。这种模式的核心思想是共享尽可能多的对象,以减少内存使用和提高性能。
"享元"这个术语源自围棋术语,表示棋子。在围棋中,棋盘上棋子数量有限,但可以组合出千变万化的棋局。享元模式的命名就体现了这种"以少御多"的思想。
2. 为什么需要享元模式?
在以下情况下,享元模式特别有用:
- 当需要创建大量相似对象时:如果这些对象的大部分状态都可以共享,那么使用享元模式可以显著减少内存消耗
- 当对象的大部分状态可以设为外部状态时:享元模式将对象的状态分为内部状态(共享)和外部状态(不共享),如果一个对象的大部分状态可以外部化,则可以使用享元模式
- 当需要维护大量小粒度对象时:如果对象数量太大,使用工厂模式会占用太多内存,这时可以考虑使用享元模式
- 当对象的身份不重要时:如果应用程序不依赖对象的身份,享元模式会更有效
3. 享元模式的核心概念
享元模式的核心是区分对象的内部状态和外部状态:
- 内部状态(Intrinsic State):储存在享元对象内部,不会随环境改变而改变的状态,可以共享。例如,字符的字形、字体。
- 外部状态(Extrinsic State):随环境改变而改变的状态,不可共享。例如,字符的位置、字号。
享元对象由内部状态唯一标识,并存储在享元池中以便复用。而外部状态则在使用时由客户端传入。
4. 享元模式的结构
享元模式通常包含以下角色:
- 享元接口(Flyweight):定义了享元类的公共方法,通过这些方法可以接受并作用于外部状态
- 具体享元(Concrete Flyweight):实现享元接口,存储内部状态
- 非共享具体享元(Unshared Concrete Flyweight):不被共享的享元实现类
- 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保共享享元对象
- 客户端(Client):维护对享元的引用,计算或存储享元的外部状态
5. 享元模式的基本实现
5.1 基础示例:字符格式化系统
假设我们正在开发一个文本编辑器,需要表示大量的字符及其格式。每个字符都有字体、大小、颜色等属性。如果为每个字符创建单独的对象,将会消耗大量内存。我们可以使用享元模式来优化。
首先,定义享元接口:
// 字符享元接口
public interface CharacterFlyweight {
// 操作方法,接收外部状态
void display(int fontSize, int x, int y);
// 获取内部状态
char getCharacter();
}
然后,实现具体享元类:
// 具体享元类
public class ConcreteCharacter implements CharacterFlyweight {
// 内部状态
private final char character;
private final String fontFamily;
private final int style; // 0=普通, 1=粗体, 2=斜体, 3=粗斜体
public ConcreteCharacter(char character, String fontFamily, int style) {
this.character = character;
this.fontFamily = fontFamily;
this.style = style;
// 模拟对象创建的开销
System.out.println("创建字符对象: " + character + ", 字体: " + fontFamily + ", 样式: " + style);
}
@Override
public void display(int fontSize, int x, int y) {
// 使用内部状态和外部状态显示字符
System.out.println("显示字符: " + character +
", 字体: " + fontFamily +
", 样式: " + convertStyleToString(style) +
", 大小: " + fontSize +
", 位置: (" + x + ", " + y + ")");
}
@Override
public char getCharacter() {
return character;
}
private String convertStyleToString(int style) {
switch (style) {
case 0: return "普通";
case 1: return "粗体";
case 2: return "斜体";
case 3: return "粗斜体";
default: return "未知";
}
}
}
接下来,创建享元工厂:
import java.util.HashMap;
import java.util.Map;
// 享元工厂
public class CharacterFlyweightFactory {
private static final Map<String, CharacterFlyweight> flyweights = new HashMap<>();
// 获取享元对象,如果不存在就创建一个新的
public static CharacterFlyweight getCharacter(char character, String fontFamily, int style) {
// 创建键
String key = character + "_" + fontFamily + "_" + style;
// 检查是否存在
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteCharacter(character, fontFamily, style));
}
return flyweights.get(key);
}
// 获取池中对象数量
public static int getFlyweightCount() {
return flyweights.size();
}
}
最后,客户端代码:
public class FlyweightPatternDemo {
public static void main(String[] args) {
// 使用享元模式显示一段文本
String text = "Hello, Flyweight Pattern!";
// 对每个字符应用不同的外部状态
for (int i = 0; i < text.length