设计模式-享元模式
要理解设计模式中的享元模式,我们可以从其核心思想入手:通过共享技术复用大量相似对象,从而减少内存消耗和对象创建成本。它的本质是分离对象的“内部状态”(可共享、不变的部分)和“外部状态”(不可共享、随场景变化的部分),让相似对象共享内部状态,仅通过外部状态区分差异。
一、享元模式的核心角色
享元模式包含4个核心角色,各自职责如下:
| 角色 | 职责描述 |
|---|---|
| 抽象享元(Flyweight) | 声明享元对象的接口,定义内部状态的访问方法,以及接收外部状态的操作方法。 |
| 具体享元(ConcreteFlyweight) | 实现抽象享元接口,存储内部状态(可共享),并在操作中结合外部状态完成业务逻辑。 |
| 享元工厂(FlyweightFactory) | 负责创建和管理享元对象,维护一个“享元池”(缓存),确保相同内部状态的对象只被创建一次。 |
| 客户端(Client) | 负责创建或获取享元对象,为享元对象传递外部状态,并使用享元对象完成操作。 |
二、Java代码演示
我们以“围棋游戏”为例(围棋有大量棋子,仅黑白两色,颜色是可共享的内部状态,位置是随落子变化的外部状态),演示享元模式的实现。
1. 定义抽象享元(Flyweight)
声明棋子的核心操作(如渲染棋子),并定义内部状态(颜色)的访问方法:
// 抽象享元:棋子
public interface ChessPiece {
// 获取内部状态(颜色)
String getColor();
// 渲染棋子(结合外部状态:位置)
void render(int x, int y);
}
2. 定义具体享元(ConcreteFlyweight)
实现抽象享元,存储内部状态(颜色),并在渲染时结合外部状态(位置):
// 具体享元:黑色棋子
public class BlackChessPiece implements ChessPiece {
// 内部状态(可共享:所有黑棋颜色相同)
private final String color = "黑色";
@Override
public String getColor() {
return color;
}
@Override
public void render(int x, int y) {
// 结合外部状态(位置x,y)执行渲染
System.out.println("渲染" + color + "棋子,位置:(" + x + "," + y + ")");
}
}
// 具体享元:白色棋子
public class WhiteChessPiece implements ChessPiece {
// 内部状态(可共享:所有白棋颜色相同)
private final String color = "白色";
@Override
public String getColor() {
return color;
}
@Override
public void render(int x, int y) {
System.out.println("渲染" + color + "棋子,位置:(" + x + "," + y + ")");
}
}
3. 定义享元工厂(FlyweightFactory)
创建并管理享元池,确保相同颜色的棋子只被实例化一次:
import java.util.HashMap;
import java.util.Map;
// 享元工厂:棋子工厂
public class ChessPieceFactory {
// 享元池(缓存棋子对象,key为颜色)
private static final Map<String, ChessPiece> chessPieces = new HashMap<>();
// 获取棋子(如果存在则复用,不存在则创建并缓存)
public static ChessPiece getChessPiece(String color) {
// 从缓存中获取
ChessPiece piece = chessPieces.get(color);
// 缓存中没有则创建
if (piece == null) {
switch (color) {
case "黑色":
piece = new BlackChessPiece();
break;
case "白色":
piece = new WhiteChessPiece();
break;
default:
throw new IllegalArgumentException("不支持的棋子颜色:" + color);
}
// 存入缓存
chessPieces.put(color, piece);
System.out.println("创建新的" + color + "棋子");
} else {
System.out.println("复用已有的" + color + "棋子");
}
return piece;
}
}
4. 客户端(Client)测试
模拟围棋落子过程,通过工厂获取棋子并传入外部状态(位置):
public class Client {
public static void main(String[] args) {
// 第一次获取黑色棋子(创建新对象)
ChessPiece black1 = ChessPieceFactory.getChessPiece("黑色");
black1.render(1, 1); // 输出:渲染黑色棋子,位置:(1,1)
// 第二次获取黑色棋子(复用已有对象)
ChessPiece black2 = ChessPieceFactory.getChessPiece("黑色");
black2.render(3, 3); // 输出:渲染黑色棋子,位置:(3,3)
// 第一次获取白色棋子(创建新对象)
ChessPiece white1 = ChessPieceFactory.getChessPiece("白色");
white1.render(2, 2); // 输出:渲染白色棋子,位置:(2,2)
// 第二次获取白色棋子(复用已有对象)
ChessPiece white2 = ChessPieceFactory.getChessPiece("白色");
white2.render(4, 4); // 输出:渲染白色棋子,位置:(4,4)
// 验证是否为同一对象(通过内存地址哈希值)
System.out.println("black1与black2是否为同一对象:" + (black1 == black2)); // true
System.out.println("white1与white2是否为同一对象:" + (white1 == white2)); // true
}
}
三、享元模式的优点
- 减少对象数量:通过共享相似对象,大幅降低系统中实例的数量,节省内存空间(如上例中,无论下多少黑棋/白棋,都只需要2个对象)。
- 提高性能:减少对象创建和垃圾回收的开销,尤其适合需要大量相似对象的场景(如游戏中的粒子、文本编辑器中的字符)。
- 分离状态:清晰区分内部状态(共享)和外部状态(独立),符合“单一职责原则”。
通过这个例子,我们可以清晰看到:享元模式通过共享可复用的内部状态,让大量相似对象只需要少量实例,从而高效解决了“对象过多导致的内存浪费”问题。它特别适合系统中存在大量细粒度对象,且这些对象的大部分状态可以共享的场景。

浙公网安备 33010602011771号