今天我们来学习最后一个设计模式:享元模式。
相对来说,享元模式的原理和实现也比较简单,并且在实际的项目开发中也不怎么常用。
概述
享元模式:(Flyweight Design Pattern)运用共享技术有效的支持大量细粒度
的对象。
所谓享元
,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。
当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。
何时使用:
- 系统中有大量对象。
- 这些对象消耗大量内存。
- 这些对象的状态大部分可以外部化。
- 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
- 系统不依赖于这些对象身份,这些对象是不可分辨的。
UML 类图:
角色组成:
-
抽象享元(Flyweight)角色: 是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
-
具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
-
非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
-
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
实例解析
假设我们要开发一个象棋
游戏。一个游戏厅中有成千上万个房间
,每个房间对应一个棋局。棋局要保存每个棋子的数据:棋子类型
、棋子颜色
、棋子在棋局中的位置
。利用这些数据,我们就能显示一个完整的棋局给玩家。
此时,我们就可以使用享元模式来实现。具体代码如下:
享元类 ChessPieceUnit.java
public class ChessPieceUnit {
private int id;
private String text;
private Color color;
public ChessPieceUnit(int id, String text, Color color) {
this.id = id;
this.text = text;
this.color = color;
}
public static enum Color {
RED, BLACK
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
棋子 ChessPiece.java
public class ChessPiece {
private ChessPieceUnit chessPieceUnit;
private int positionX;
private int positionY;
public ChessPiece(ChessPieceUnit chessPieceUnit, int positionX, int positionY) {
this.chessPieceUnit = chessPieceUnit;
this.positionX = positionX;
this.positionY = positionY;
}
public ChessPieceUnit getChessPieceUnit() {
return chessPieceUnit;
}
public void setChessPieceUnit(ChessPieceUnit chessPieceUnit) {
this.chessPieceUnit = chessPieceUnit;
}
public int getPositionX() {
return positionX;
}
public void setPositionX(int positionX) {
this.positionX = positionX;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
}
享元工厂 ChessPieceUnitFactory.java
public class ChessPieceUnitFactory {
private static final Map<Integer, ChessPieceUnit> pieces = new HashMap<>();
static {
pieces.put(1, new ChessPieceUnit(1,"車", ChessPieceUnit.Color.BLACK));
pieces.put(1, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK));
pieces.put(1, new ChessPieceUnit(3,"相", ChessPieceUnit.Color.BLACK));
// 省略其他棋子代码。。。
}
public static ChessPieceUnit getChessPiece(int chessPieceId) {
return pieces.get(chessPieceId);
}
}
棋局 ChessBoard.java
public class ChessBoard {
private Map<Integer, ChessPiece> chessPieces = new HashMap<>();
public ChessBoard() {
init();
}
private void init() {
chessPieces.put(1, new ChessPiece(ChessPieceUnitFactory.getChessPiece(1), 0, 0));
chessPieces.put(2, new ChessPiece(ChessPieceUnitFactory.getChessPiece(2), 1, 0));
chessPieces.put(3, new ChessPiece(ChessPieceUnitFactory.getChessPiece(3), 2, 0));
// 省略其他棋子代码。。。
}
public void move(int chessPieceId, int toPositionX, int toPositionY) {
// 省略。。。
}
}
总结
优缺点
-
优点: 大大减少对象的创建,降低系统的内存,使效率提高。
-
缺点: 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
应用
- String 常量池、数据库连接池、缓冲池等等。
- Java Integer 类中、String 类的
字符串常量池
。
实际上,享元模式对 JVM 的垃圾回收并不友好。因为享元工厂类一直保存了对享元对象的引用,这就导致享元对象在没有任何代码使用的情况下,也并不会被 JVM 垃圾回收机制自动回收掉。因此,在某些情况下,如果对象的生命周期很短,也不会被密集使用,利用享元模式反倒可能会浪费更多的内存。所以,除非经过线上验证,利用享元模式真的可以大大节省内存,否则,就不要过度使用这个模式,为了一点点内存的节省而引入一个复杂的设计模式,得不偿失。