享元模式:运用共享技术,有效支持大量细粒度对象。区分内部状态(可共享,不变)和外部状态(不可共享,由外部传入),减少对象创建数量,降低内存占用。
适用场景:系统需要创建大量同类对象,造成内存压力;对象大部分状态可以外部化。
UML 角色
Flyweight(抽象享元):定义接口,接收外部状态
ConcreteFlyweight(具体享元):保存内部状态,可共享
UnsharedConcreteFlyweight(非共享享元):不需要共享的对象
FlyweightFactory(享元工厂):管理享元池,复用对象
完整示范代码
import java.util.HashMap;
import java.util.Map;
/**
* 抽象享元角色:棋子
* 内部状态:棋子类型(黑棋/白棋,不变,可以共享)
* 外部状态:坐标 x,y(位置会变,不存到对象内部,调用时传入)
*/
public interface ChessFlyweight {
// 传入外部状态:坐标
void display(int x, int y);
}
/**
* 具体享元:围棋棋子,可共享
*/
class ConcreteChess implements ChessFlyweight {
// 内部状态:棋子颜色,固定不变,可以共享
private final String color;
public ConcreteChess(String color) {
this.color = color;
}
@Override
public void display(int x, int y) {
System.out.printf("棋子颜色:%s,位置:(x=%d, y=%d),对象哈希:%d%n",
color, x, y, this.hashCode());
}
}
/**
* 享元工厂:负责创建、缓存棋子对象
*/
class ChessFlyweightFactory {
// 享元池,缓存已经创建好的棋子
private final Map<String, ChessFlyweight> flyweightPool = new HashMap<>();
// 获取棋子,存在就复用,不存在新建
public ChessFlyweight getChess(String color) {
if (!flyweightPool.containsKey(color)) {
flyweightPool.put(color, new ConcreteChess(color));
System.out.println("新建棋子对象:" + color);
} else {
System.out.println("复用已有棋子对象:" + color);
}
return flyweightPool.get(color);
}
// 获取池内对象数量
public int getPoolSize() {
return flyweightPool.size();
}
}
// 测试客户端
class FlyweightDemo {
public static void main(String[] args) {
ChessFlyweightFactory factory = new ChessFlyweightFactory();
// 多次落子,大量棋子,实际只创建2个对象(黑、白)
ChessFlyweight c1 = factory.getChess("黑色");
c1.display(1,1);
ChessFlyweight c2 = factory.getChess("黑色");
c2.display(2,3);
ChessFlyweight c3 = factory.getChess("白色");
c3.display(5,5);
ChessFlyweight c4 = factory.getChess("白色");
c4.display(7,8);
System.out.println("享元池对象总数:" + factory.getPoolSize());
// c1 和 c2 hashcode 完全相同,是同一个对象;c3 c4 同一个对象
}
}
输出结果
新建棋子对象:黑色
棋子颜色:黑色,位置:(x=1, y=1),对象哈希:123456
复用已有棋子对象:黑色
棋子颜色:黑色,位置:(x=2, y=3),对象哈希:123456
新建棋子对象:白色
棋子颜色:白色,位置:(x=5, y=5),对象哈希:789012
复用已有棋子对象:白色
棋子颜色:白色,位置:(x=7, y=8),对象哈希:789012
享元池对象总数:2
可以看到:虽然落子 4 次,但是只实例化 2 个对象,坐标 x,y 作为外部状态每次调用传入,对象本身被复用。
内部状态 vs 外部状态
表格
JDK 中享元模式典型例子
Integer.valueOf() 缓存 -128~127,就是享元模式:
Integer a = Integer.valueOf(10);
Integer b = Integer.valueOf(10);
// a == b ,复用缓存对象
优缺点
✅优点:
极大减少内存中对象数量,降低内存开销
对象复用,性能提升
❌缺点:
需要区分内外状态,增加代码复杂度
外部状态由客户端维护,容易出错
使用注意
不要把变化的数据放到享元对象内部;变化数据全部作为参数传入方法。
本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。
原文链接
欢迎访问