一、模式定义与核心思想
享元模式通过共享技术有效地支持大量细粒度对象的复用。它使用共享对象来最小化内存使用或计算开销,特别适用于存在大量相似对象的场景。享元模式的核心是将对象的状态分为内部状态(Intrinsic State)和外部状态(Extrinsic State)。
- 内部状态:对象共享的、不可变的部分,存储在享元对象内部
- 外部状态:对象特有的、可变的部分,由客户端在调用时传入
二、Java源代码实现
1. 基础享元模式实现
java
java
下载
复制
import java.util.HashMap;
import java.util.Map;
/**
* 享元接口
*/
interface Shape {
void draw(int x, int y, String color); // 外部状态作为参数传入
}
/**
* 具体享元类 - 圆形
*/
class Circle implements Shape {
private String type; // 内部状态:类型(共享)
private String color; // 内部状态:颜色(共享)
private int radius; // 内部状态:半径(共享)
public Circle(String color) {
this.type = "Circle";
this.color = color;
this.radius = 10; // 默认半径
}
@Override
public void draw(int x, int y, String borderColor) {
System.out.println(String.format(
"绘制%s: 颜色=%s, 边框色=%s, 半径=%d, 位置=(%d, %d)",
type, color, borderColor, radius, x, y
));
}
// 内部状态getter
public String getColor() {
return color;
}
}
/**
* 享元工厂 - 管理共享的享元对象
*/
class ShapeFactory {
private static final Map<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
// 使用颜色作为键来查找圆对象
Circle circle = (Circle) circleMap.get(color);
if (circle == null) {
// 如果不存在,则创建新对象并放入池中
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("创建新的圆形,颜色: " + color);
} else {
System.out.println("复用已有圆形,颜色: " + color);
}
return circle;
}
public static int getCircleCount() {
return circleMap.size();
}
}
2. 复杂享元模式实现(带外部状态)
java
java
下载
复制
import java.util.ArrayList;
import java.util.List;
/**
* 享元对象 - 字符对象
*/
class CharacterFlyweight {
private char symbol; // 内部状态:字符(共享)
private String fontFamily; // 内部状态:字体(共享)
private int fontSize; // 内部状态:字号(共享)
public CharacterFlyweight(char symbol, String fontFamily, int fontSize) {
this.symbol = symbol;
this.fontFamily = fontFamily;
this.fontSize = fontSize;
}
public void render(int positionX, int positionY, String color) {
// 外部状态:位置和颜色
System.out.println(String.format(
"渲染字符 '%c' [字体: %s, 字号: %d] 在位置(%d, %d),颜色: %s",
symbol, fontFamily, fontSize, positionX, positionY, color
));
}
// 内部状态唯一标识
@Override
public String toString() {
return symbol + "-" + fontFamily + "-" + fontSize;
}
}
/**
* 享元工厂 - 带缓存管理
*/
class CharacterFlyweightFactory {
private static final Map<String, CharacterFlyweight> cache = new HashMap<>();
public static CharacterFlyweight getCharacter(char symbol, String fontFamily, int fontSize) {
String key = symbol + "-" + fontFamily + "-" + fontSize;
if (!cache.containsKey(key)) {
cache.put(key, new CharacterFlyweight(symbol, fontFamily, fontSize));
System.out.println("创建新字符对象: " + key);
}
return cache.get(key);
}
public static int getCacheSize() {
return cache.size();
}
public static void clearCache() {
cache.clear();
System.out.println("缓存已清空");
}
}
/**
* 外部状态容器 - 维护外部状态与享元对象的关联
*/
class TextCharacter {
private CharacterFlyweight flyweight; // 享元对象(内部状态)
private int positionX; // 外部状态
private int positionY; // 外部状态
private String color; // 外部状态
public TextCharacter(CharacterFlyweight flyweight, int positionX, int positionY, String color) {
this.flyweight = flyweight;
this.positionX = positionX;
this.positionY = positionY;
this.color = color;
}
public void render() {
flyweight.render(positionX, positionY, color);
}
}
/**
* 文档对象 - 使用享元模式
*/
class Document {
private List<TextCharacter> characters = new ArrayList<>();
public void addCharacter(char symbol, String fontFamily, int fontSize,
int x, int y, String color) {
// 获取或创建享元对象
CharacterFlyweight flyweight = CharacterFlyweightFactory.getCharacter(
symbol, fontFamily, fontSize
);
// 创建包含外部状态的对象
TextCharacter textChar = new TextCharacter(flyweight, x, y, color);
characters.add(textChar);
}
public void render() {
System.out.println("\n===== 渲染文档 =====");
for (TextCharacter ch : characters) {
ch.render();
}
System.out.println("总字符数: " + characters.size());
System.out.println("享元对象数: " + CharacterFlyweightFactory.getCacheSize());
System.out.println("内存节省: " +
(characters.size() - CharacterFlyweightFactory.getCacheSize()) + " 个对象");
}
}
3. 客户端调用示例
java
java
下载
复制
public class Client {
public static void main(String[] args) {
System.out.println("===== 示例1: 基础享元模式 =====");
demoBasicFlyweight();
System.out.println("\n===== 示例2: 复杂享元模式 =====");
demoComplexFlyweight();
System.out.println("\n===== 示例3: 性能对比 =====");
demoPerformanceComparison();
}
private static void demoBasicFlyweight() {
// 获取圆形对象
Shape redCircle1 = ShapeFactory.getCircle("红色");
redCircle1.draw(10, 10, "黑色");
Shape redCircle2 = ShapeFactory.getCircle("红色"); // 复用
redCircle2.draw(20, 20, "白色");
Shape blueCircle = ShapeFactory.getCircle("蓝色"); // 新建
blueCircle.draw(30, 30, "黑色");
Shape greenCircle1 = ShapeFactory.getCircle("绿色"); // 新建
greenCircle1.draw(40, 40, "黄色");
Shape greenCircle2 = ShapeFactory.getCircle("绿色"); // 复用
greenCircle2.draw(50, 50, "红色");
System.out.println("圆形对象总数: 5");
System.out.println("实际创建的圆形对象数: " + ShapeFactory.getCircleCount());
}
private static void demoComplexFlyweight() {
Document doc = new Document();
// 添加字符到文档
doc.addCharacter('H', "Arial", 12, 0, 0, "黑色");
doc.addCharacter('e', "Arial", 12, 10, 0, "黑色");
doc.addCharacter('l', "Arial", 12, 20, 0, "黑色");
doc.addCharacter('l', "Arial", 12, 30, 0, "红色"); // 相同字符,不同颜色
doc.addCharacter('o', "Arial", 12, 40, 0, "黑色");
doc.addCharacter('W', "Times New Roman", 14, 0, 20, "蓝色");
doc.addCharacter('o', "Times New Roman", 14, 15, 20, "蓝色");
doc.addCharacter('r', "Times New Roman", 14, 30, 20, "蓝色");
doc.addCharacter('l', "Arial", 12, 45, 20, "绿色"); // 相同字符,不同字体
doc.addCharacter('d', "Times New Roman", 14, 60, 20, "蓝色");
// 渲染文档
doc.render();
}
private static void demoPerformanceComparison() {
System.out.println("非享元方式创建10000个字符对象:");
long start1 = System.currentTimeMillis();
createCharactersWithoutFlyweight(10000);
long end1 = System.currentTimeMillis();
System.out.println("耗时: " + (end1 - start1) + "ms");
System.out.println("内存使用: 10000个对象");
System.out.println("\n享元方式创建10000个字符对象:");
long start2 = System.currentTimeMillis();
createCharactersWithFlyweight(10000);
long end2 = System.currentTimeMillis();
System.out.println("耗时: " + (end2 - start2) + "ms");
System.out.println("内存使用: " + CharacterFlyweightFactory.getCacheSize() + "个对象");
// 清理缓存
CharacterFlyweightFactory.clearCache();
}
private static void createCharactersWithoutFlyweight(int count) {
// 非享元方式:每个字符都创建新对象
List<Character> characters = new ArrayList<>();
char[] symbols = {'A', 'B', 'C', 'D', 'E'};
for (int i = 0; i < count; i++) {
char symbol = symbols[i % symbols.length];
// 每次都创建新对象
CharacterFlyweight ch = new CharacterFlyweight(symbol, "Arial", 12);
characters.add((Character) null); // 模拟使用
}
}
private static void createCharactersWithFlyweight(int count) {
// 享元方式:复用对象
char[] symbols = {'A', 'B', 'C', 'D', 'E'};
for (int i = 0; i < count; i++) {
char symbol = symbols[i % symbols.length];
// 从工厂获取,会复用已存在的对象
CharacterFlyweight ch = CharacterFlyweightFactory.getCharacter(symbol, "Arial", 12);
}
}
}
三、应用场景深度解析
1. 文本编辑器/文档处理系统
java
java
下载
复制
/**
* 实际应用:富文本编辑器
*/
class RichTextEditor {
private static class FontStyle {
String family;
int size;
boolean bold;
boolean italic;
FontStyle(String family, int size, boolean bold, boolean italic) {
this.family = family;
this.size = size;
this.bold = bold;
this.italic = italic;
}
@Override
public int hashCode() {
return (family + size + bold + italic).hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof FontStyle)) return false;
FontStyle other = (FontStyle) obj;
return family.equals(other.family) && size == other.size &&
bold == other.bold && italic == other.italic;
}
}
private static class FontStyleFactory {
private static final Map<Integer, FontStyle> cache = new HashMap<>();
public static FontStyle getFontStyle(String family, int size,
boolean bold, boolean italic) {
FontStyle style = new FontStyle(family, size, bold, italic);
int key = style.hashCode();
if (!cache.containsKey(key)) {
cache.put(key, style);
}
return cache.get(key);
}
}
}
2. 游戏开发 - 粒子系统/单位管理
java
java
下载
复制
/**
* 游戏单位享元模式
*/
class GameUnitFlyweight {
// 内部状态(共享)
private String unitType; // 单位类型
private String texture; // 纹理
private int baseHealth; // 基础生命值
private int baseDamage; // 基础攻击力
private int moveSpeed; // 移动速度
public GameUnitFlyweight(String unitType, String texture) {
this.unitType = unitType;
this.texture = texture;
// 根据类型设置基础属性
switch(unitType) {
case "Archer":
this.baseHealth = 50;
this.baseDamage = 15;
this.moveSpeed = 3;
break;
case "Warrior":
this.baseHealth = 100;
this.baseDamage = 25;
this.moveSpeed = 2;
break;
case "Mage":
this.baseHealth = 40;
this.baseDamage = 30;
this.moveSpeed = 2;
break;
}
}
public void render(int x, int y, int health, int level) {
// 外部状态:位置、当前生命值、等级
System.out.println(String.format(
"渲染%s [纹理: %s] 在(%d, %d), 生命: %d/%d, 等级: %d",
unitType, texture, x, y, health, baseHealth, level
));
}
}
/**
* 游戏单位实例(包含外部状态)
*/
class GameUnit {
private GameUnitFlyweight flyweight; // 享元对象
private int x, y; // 外部状态:位置
private int currentHealth; // 外部状态:当前生命值
private int level; // 外部状态:等级
private String owner; // 外部状态:所有者
public GameUnit(GameUnitFlyweight flyweight, int x, int y, String owner) {
this.flyweight = flyweight;
this.x = x;
this.y = y;
this.currentHealth = flyweight.baseHealth;
this.level = 1;
this.owner = owner;
}
public void render() {
flyweight.render(x, y, currentHealth, level);
}
}
3. GUI组件库
java
java
下载
复制
/**
* GUI按钮享元模式
*/
class ButtonFlyweight {
// 内部状态
private String style; // 样式主题
private String font; // 字体
private String backgroundColor; // 背景色
private String borderStyle; // 边框样式
public ButtonFlyweight(String style) {
this.style = style;
// 根据样式初始化
if ("Modern".equals(style)) {
this.font = "Arial";
this.backgroundColor = "#3498db";
this.borderStyle = "rounded";
} else if ("Classic".equals(style)) {
this.font = "Times New Roman";
this.backgroundColor = "#2ecc71";
this.borderStyle = "square";
}
}
public void render(String text, int x, int y, int width, int height) {
// 外部状态:文本、位置、尺寸
System.out.println(String.format(
"渲染%s按钮: '%s' 在(%d, %d), 尺寸: %dx%d\n" +
"字体: %s, 背景: %s, 边框: %s",
style, text, x, y, width, height,
font, backgroundColor, borderStyle
));
}
}
4. 数据库连接池
java
java
下载
复制
/**
* 连接池享元模式实现
*/
class ConnectionFlyweight {
// 内部状态
private String url;
private String username;
private String password;
private String driverClass;
// 实际连接(模拟)
private boolean inUse;
public ConnectionFlyweight(String url, String username,
String password, String driverClass) {
this.url = url;
this.username = username;
this.password = password;
this.driverClass = driverClass;
this.inUse = false;
System.out.println("创建数据库连接: " + url);
}
public void executeQuery(String sql, Map<String, Object> params) {
// 外部状态:SQL和参数
System.out.println("执行查询: " + sql);
System.out.println("参数: " + params);
System.out.println("使用连接: " + url);
}
public boolean isInUse() {
return inUse;
}
public void setInUse(boolean inUse) {
this.inUse = inUse;
}
}
class ConnectionPool {
private static final Map<String, List<ConnectionFlyweight>> pools = new HashMap<>();
private static final int MAX_POOL_SIZE = 10;
public static synchronized ConnectionFlyweight getConnection(
String url, String username, String password, String driverClass) {
String key = url + username + driverClass;
List<ConnectionFlyweight> pool = pools.getOrDefault(key, new ArrayList<>());
// 查找空闲连接
for (ConnectionFlyweight conn : pool) {
if (!conn.isInUse()) {
conn.setInUse(true);
System.out.println("复用现有连接");
return conn;
}
}
// 创建新连接(如果未达到上限)
if (pool.size() < MAX_POOL_SIZE) {
ConnectionFlyweight newConn = new ConnectionFlyweight(
url, username, password, driverClass
);
newConn.setInUse(true);
pool.add(newConn);
pools.put(key, pool);
return newConn;
}
throw new RuntimeException("连接池已满");
}
public static void releaseConnection(ConnectionFlyweight conn) {
conn.setInUse(false);
}
}
四、优缺点深度解析
优点
-
极大减少内存使用
- 共享内部状态,避免重复创建相同对象
- 对于大量相似对象,内存节省效果显著
- 示例:10000个相同字符对象 → 1个享元对象 + 10000个外部状态
-
提高性能
- 减少对象创建和销毁的开销
- 降低垃圾回收压力
- 特别适合创建成本高的对象
-
统一管理共享对象
- 集中控制共享对象的生命周期
- 便于实现对象池、缓存等机制
- 支持对象的延迟初始化
-
减少系统复杂度
- 将对象状态分离为内部和外部
- 客户端代码更简洁
- 便于维护和扩展
-
支持大规模对象集合
- 能够处理传统方式无法处理的大规模对象
- 在内存有限的情况下仍能工作
缺点
-
增加系统复杂度
- 需要区分内部状态和外部状态
- 增加了设计和实现的难度
- 可能引入线程安全问题
-
外部状态管理复杂
- 客户端需要维护外部状态
- 外部状态的传递增加了方法参数
- 可能影响代码可读性
-
线程安全问题
- 共享对象可能被多个线程同时访问
- 需要额外的同步机制
- 可能降低并发性能
-
不适用于所有场景
- 对象差异较大时效果有限
- 外部状态过多可能抵消收益
- 不适合需要独立对象实例的场景
-
调试困难
- 对象状态分散在内部和外部
- 调试时难以追踪完整状态
- 可能引入隐式错误
五、使用要点与最佳实践
1. 正确识别内部状态和外部状态
java
java
下载
复制
/**
* 正确识别状态的示例
*/
class GoodFlyweightDesign {
// 内部状态(共享,不变)
static class IntrinsicState {
String type; // 类型
String texture; // 纹理
String model; // 模型
int baseAttributes; // 基础属性
@Override
public boolean equals(Object o) {
// 基于内部状态实现equals和hashCode
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IntrinsicState that = (IntrinsicState) o;
return Objects.equals(type, that.type) &&
Objects.equals(texture, that.texture) &&
Objects.equals(model, that.model);
}
@Override
public int hashCode() {
return Objects.hash(type, texture, model);
}
}
// 外部状态(不共享,变化)
static class ExtrinsicState {
int positionX;
int positionY;
int currentHealth;
String owner;
// ... 其他变化的状态
}
}
2. 工厂模式与享元模式的结合
java
java
下载
复制
/**
* 享元工厂的最佳实践
*/
class OptimizedFlyweightFactory {
// 使用WeakReference避免内存泄漏
private static final Map<String, WeakReference<Flyweight>> cache =
new ConcurrentHashMap<>();
// 使用双重检查锁定确保线程安全
public static Flyweight getFlyweight(String key) {
WeakReference<Flyweight> ref = cache.get(key);
Flyweight flyweight = (ref != null) ? ref.get() : null;
if (flyweight == null) {
synchronized (OptimizedFlyweightFactory.class) {
ref = cache.get(key);
flyweight = (ref != null) ? ref.get() : null;
if (flyweight == null) {
flyweight = createFlyweight(key);
cache.put(key, new WeakReference<>(flyweight));
}
}
}
return flyweight;
}
// 定期清理失效的引用
public static void cleanup() {
cache.entrySet().removeIf(entry ->
entry.getValue().get() == null
);
}
}
3. 线程安全实现
java
java
下载
复制
/**
* 线程安全的享元模式
*/
class ThreadSafeFlyweight {
// 使用ConcurrentHashMap保证线程安全
private static final ConcurrentHashMap<String, Flyweight> cache =
new ConcurrentHashMap<>();
// 使用computeIfAbsent原子操作
public static Flyweight getFlyweight(String key) {
return cache.computeIfAbsent(key, k -> {
System.out.println("创建新享元对象: " + k);
return createFlyweight(k);
});
}
// 使用不可变对象作为享元
static class ImmutableFlyweight {
private final String data; // final保证不可变
public ImmutableFlyweight(String data) {
this.data = data;
}
// 只读方法,线程安全
public void operation(String extrinsicState) {
System.out.println("处理: " + data + " with " + extrinsicState);
}
}
}
4. 性能优化技巧
java
java
下载
复制
class PerformanceOptimizedFlyweight {
// 1. 使用枚举实现单例享元
enum SingletonFlyweight implements Flyweight {
INSTANCE_A,
INSTANCE_B,
INSTANCE_C;
@Override
public void operation(String state) {
// 实现
}
}
// 2. 使用享元池控制对象数量
static class FlyweightPool {
private final BlockingQueue<Flyweight> pool;
private final int maxSize;
public FlyweightPool(int maxSize, Supplier<Flyweight> creator) {
this.maxSize = maxSize;
this.pool = new ArrayBlockingQueue<>(maxSize);
// 预创建对象
for (int i = 0; i < maxSize / 2; i++) {
pool.offer(creator.get());
}
}
public Flyweight acquire() throws InterruptedException {
Flyweight flyweight = pool.poll();
if (flyweight == null) {
flyweight = createFlyweight();
}
return flyweight;
}
public void release(Flyweight flyweight) {
if (pool.size() < maxSize) {
pool.offer(flyweight);
}
}
}
// 3. 使用缓存策略(LRU)
static class LRUFlyweightCache {
private final LinkedHashMap<String, Flyweight> cache;
private final int capacity;
public LRUFlyweightCache(int capacity) {
this.capacity = capacity;
this.cache = new LinkedHashMap<String, Flyweight>(
capacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity;
}
};
}
public synchronized Flyweight get(String key) {
return cache.get(key);
}
public synchronized void put(String key, Flyweight value) {
cache.put(key, value);
}
}
}
六、与其他模式的关系与对比
1. 享元模式 vs 单例模式
| 特性 | 享元模式 | 单例模式 |
|---|---|---|
| 目的 | 对象共享,减少内存使用 | 全局唯一实例 |
| 对象数量 | 多个(按类别) | 一个 |
| 状态 | 有内部状态和外部状态 | 通常无状态或有全局状态 |
| 使用场景 | 大量相似对象 | 全局访问点 |
2. 享元模式 vs 原型模式
| 特性 | 享元模式 | 原型模式 |
|---|---|---|
| 目的 | 共享对象,减少创建 | 快速创建相似对象 |
| 对象关系 | 共享同一对象 | 克隆新对象 |
| 状态处理 | 分离内部/外部状态 | 完全复制状态 |
| 性能 | 内存优化 | 创建性能优化 |
3. 享元模式与组合模式结合
java
java
下载
复制
/**
* 享元模式 + 组合模式:文档结构树
*/
class CompositeFlyweightExample {
// 组件接口
interface DocumentComponent {
void render(String style);
}
// 叶子节点 - 字符(享元)
static class Character implements DocumentComponent {
private char symbol;
private FontFlyweight font; // 享元对象
public Character(char symbol, FontFlyweight font) {
this.symbol = symbol;
this.font = font;
}
@Override
public void render(String style) {
font.render(symbol, style);
}
}
// 复合节点 - 段落
static class Paragraph implements DocumentComponent {
private List<DocumentComponent> children = new ArrayList<>();
public void add(DocumentComponent component) {
children.add(component);
}
@Override
public void render(String style) {
for (DocumentComponent child : children) {
child.render(style);
}
}
}
}
七、实际框架中的应用
1. Java String池
java
java
下载
复制
/**
* Java String的享元模式实现
*/
class StringFlyweightExample {
public static void demoStringPool() {
// String内部使用享元模式
String s1 = "Hello"; // 放入字符串常量池
String s2 = "Hello"; // 复用常量池中的"Hello"
String s3 = new String("Hello"); // 创建新对象
System.out.println("s1 == s2: " + (s1 == s2)); // true,相同对象
System.out.println("s1 == s3: " + (s1 == s3)); // false,不同对象
System.out.println("s1.equals(s3): " + s1.equals(s3)); // true,内容相同
// intern()方法返回字符串在常量池中的引用
String s4 = s3.intern();
System.out.println("s1 == s4: " + (s1 == s4)); // true
}
}
2. Integer缓存
java
java
下载
复制
/**
* Java Integer缓存(享元模式)
*/
class IntegerCacheExample {
public static void demoIntegerCache() {
// Integer缓存-128到127
Integer i1 = 127;
Integer i2 = 127;
Integer i3 = 128;
Integer i4 = 128;
System.out.println("i1 == i2 (127): " + (i1 == i2)); // true,缓存命中
System.out.println("i3 == i4 (128): " + (i3 == i4)); // false,超出缓存范围
// 手动获取缓存
Integer i5 = Integer.valueOf(100); // 使用缓存
Integer i6 = new Integer(100); // 创建新对象
System.out.println("i5 == i6: " + (i5 == i6)); // false
}
}
3. 数据库连接池实现
java
java
下载
复制
/**
* 实际数据库连接池实现(简化版)
*/
class RealConnectionPool {
private final BlockingQueue<Connection> pool;
private final int maxSize;
private final String url;
private final String username;
private final String password;
// 享元:连接配置(内部状态)
private static class ConnectionConfig {
final String url;
final String driver;
final Properties properties;
ConnectionConfig(String url, String driver, Properties props) {
this.url = url;
this.driver = driver;
this.properties = props;
}
@Override
public boolean equals(Object o) {
// 基于配置判断是否相等
if (this == o) return true;
if (!(o instanceof ConnectionConfig)) return false;
ConnectionConfig that = (ConnectionConfig) o;
return url.equals(that.url) &&
driver.equals(that.driver) &&
properties.equals(that.properties);
}
@Override
public int hashCode() {
return Objects.hash(url, driver, properties);
}
}
// 连接工厂(享元工厂)
private static class ConnectionFactory {
private static final Map<ConnectionConfig, RealConnectionPool> pools =
new HashMap<>();
public static synchronized RealConnectionPool getPool(
String url, String username, String password,
String driver, int maxSize) {
Properties props = new Properties();
props.setProperty("user", username);
props.setProperty("password", password);
ConnectionConfig config = new ConnectionConfig(url, driver, props);
if (!pools.containsKey(config)) {
pools.put(config, new RealConnectionPool(url, username, password, maxSize));
}
return pools.get(config);
}
}
}
八、反模式与常见错误
1. 错误:混淆内部状态和外部状态
java
java
下载
复制
class BadFlyweightDesign {
// 错误:将可变状态放在享元中
class BadFlyweight {
String sharedData; // 内部状态
String changingData; // 错误:可变状态不应在享元中
void operation() {
// 这里会修改共享状态!
changingData = "modified";
}
}
// 正确做法
class GoodFlyweight {
final String sharedData; // 内部状态,final确保不变
GoodFlyweight(String data) {
this.sharedData = data;
}
void operation(String extrinsicData) { // 外部状态作为参数
// 使用但不修改共享状态
System.out.println(sharedData + " with " + extrinsicData);
}
}
}
2. 错误:过度使用享元
java
java
下载
复制
class OveruseFlyweight {
// 错误:为每个小对象都使用享元
class TinyObject {
int value; // 只有4字节
TinyObject(int value) {
this.value = value;
}
}
// 问题:享元工厂本身开销可能超过节省的内存
class TinyObjectFactory {
Map<Integer, TinyObject> cache = new HashMap<>();
TinyObject getTinyObject(int value) {
return cache.computeIfAbsent(value, TinyObject::new);
}
}
// 正确:只有对象创建成本高或内存占用大时才使用享元
}
3. 错误:忽略线程安全
java
java
下载
复制
class ThreadUnsafeFlyweight {
static class UnsafeFactory {
private static Map<String, Object> cache = new HashMap<>(); // 非线程安全
public static Object get(String key) {
// 竞态条件:多个线程可能同时创建对象
if (!cache.containsKey(key)) {
cache.put(key, new ExpensiveObject());
}
return cache.get(key);
}
}
static class SafeFactory {
private static final ConcurrentHashMap<String, Object> cache =
new ConcurrentHashMap<>();
public static Object get(String key) {
// 使用computeIfAbsent保证原子性
return cache.computeIfAbsent(key, k -> new ExpensiveObject());
}
}
}
九、性能测试与评估
java
java
下载
复制
class FlyweightBenchmark {
static class ExpensiveObject {
private final byte[] data = new byte[1024 * 1024]; // 1MB
private final String id;
public ExpensiveObject(String id) {
this.id = id;
// 模拟昂贵的初始化
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
}
public static void main(String[] args) {
int objectCount = 1000;
String[] ids = {"A", "B", "C", "D", "E"}; // 只有5种类型
System.out.println("测试对象数: " + objectCount);
System.out.println("对象类型数: " + ids.length);
// 测试非享元模式
System.out.println("\n=== 非享元模式 ===");
long start1 = System.currentTimeMillis();
long memory1 = Runtime.getRuntime().freeMemory();
List<ExpensiveObject> list1 = new ArrayList<>();
for (int i = 0; i < objectCount; i++) {
String id = ids[i % ids.length];
list1.add(new ExpensiveObject(id));
}
long time1 = System.currentTimeMillis() - start1;
long memoryUsed1 = memory1 - Runtime.getRuntime().freeMemory();
System.out.println("耗时: " + time1 + "ms");
System.out.println("内存使用: " + memoryUsed1 / 1024 / 1024 + "MB");
// 测试享元模式
System.out.println("\n=== 享元模式 ===");
long start2 = System.currentTimeMillis();
long memory2 = Runtime.getRuntime().freeMemory();
Map<String, ExpensiveObject> flyweightCache = new HashMap<>();
List<ExpensiveObject> list2 = new ArrayList<>();
for (int i = 0; i < objectCount; i++) {
String id = ids[i % ids.length];
ExpensiveObject obj = flyweightCache.get(id);
if (obj == null) {
obj = new ExpensiveObject(id);
flyweightCache.put(id, obj);
}
list2.add(obj);
}
long time2 = System.currentTimeMillis() - start2;
long memoryUsed2 = memory2 - Runtime.getRuntime().freeMemory();
System.out.println("耗时: " + time2 + "ms");
System.out.println("内存使用: " + memoryUsed2 / 1024 / 1024 + "MB");
System.out.println("\n=== 性能对比 ===");
System.out.println("时间节省: " + (time1 - time2) + "ms (" +
(int)((1 - (double)time2/time1) * 100) + "%)");
System.out.println("内存节省: " + (memoryUsed1 - memoryUsed2) / 1024 / 1024 +
"MB (" + (int)((1 - (double)memoryUsed2/memoryUsed1) * 100) + "%)");
}
}
十、总结与最佳实践指南
1. 何时使用享元模式
- ✅ 对象数量巨大:系统需要创建大量相似对象
- ✅ 内存是瓶颈:对象占用内存大,需要优化内存使用
- ✅ 对象状态可分离:能够清晰区分内部状态和外部状态
- ✅ 对象大部分状态可共享:只有少部分状态需要变化
- ✅ 对象创建成本高:创建对象需要大量资源或时间
2. 何时避免享元模式
- ❌ 对象差异大:每个对象都有独特的状态
- ❌ 外部状态过多:外部状态复杂,管理成本高
- ❌ 线程安全要求高:难以保证共享对象的线程安全
- ❌ 性能不是关键问题:系统内存充足,不需要优化
- ❌ 对象生命周期短:频繁创建销毁,享元收益有限
3. 实施步骤
- 识别可共享状态:分析对象,找出不变的部分作为内部状态
- 设计享元接口:定义操作外部状态的方法
- 实现具体享元:存储内部状态,实现操作方法
- 创建享元工厂:管理享元对象的创建和共享
- 客户端使用:通过工厂获取享元,传递外部状态
4. 性能调优建议
- 监控缓存命中率:确保享元模式真正带来性能提升
- 合理设置缓存大小:避免内存泄漏或缓存过大
- 使用弱引用:防止缓存导致的内存泄漏
- 考虑并发性能:在高并发场景下优化同步机制
- 定期清理缓存:移除不再使用的享元对象
5. 与其他模式结合
- 工厂模式:管理享元对象的创建
- 组合模式:构建树形结构的享元对象
- 状态模式:享元对象内部状态变化
- 策略模式:动态改变享元对象的行为
享元模式是优化系统性能的重要工具,特别是在处理大量相似对象的场景中。正确应用享元模式可以显著减少内存使用、提高性能,但需要仔细设计内部状态和外部状态的分离,并处理好线程安全和对象生命周期管理。在实际项目中,享元模式经常与缓存、对象池等技术结合使用,是高性能系统设计的关键模式之一。