享元设计模式
小伙伴们!今天要和大家聊聊一个既实用又有点儿冷门的设计模式——享元模式!它就像内存管理的小秘书,帮我们节省内存的贴心小能手!简单概括,享元模式就是通过复用那些不可变的对象,让你的程序内存占用更小,运行更流畅!
问题一:为什么要用享元模式?
假设你正在开发一个棋牌游戏,比如象棋或麻将。每个房间都需要一个棋盘,每个棋盘上有大量的棋子。如果每个棋盘都重新创建一遍棋子,内存压力可不小!这时候,你是不是想问:“有没有什么办法能让我少造几个棋子?” 享元模式就来帮你!
问题二:享元模式是如何实现的?
举个例子:在象棋中,每个棋子都有固定的属性(比如颜色、类型)。我们只需要在内存中创建一次这些固定的棋子,然后让所有棋盘引用它。这样,无论多少个棋盘,棋子的属性都不再重复存储!剩下的只需要记录它们的位置信息就可以了!
问题三:为什么享元对象必须是不可变的?
答案很简单:因为它们要被多处共享!如果一个共享对象的状态被修改了,可能会“牵一发而动全身”,影响所有引用它的地方。所以,不可变对象是享元模式的关键!
概括来讲,: 享元模式就像一个“共享仓库”,让你的程序更高效!无论是游戏开发还是其他场景,只要遇到大量重复的对象,不妨考虑用它来优化内存!当然,也要记得,不是所有对象都适合共享哦!只有那些状态固定、属性不变的才适合当“享元”!快来试试吧,让你的程序更轻盈!
目录
前言
享元模式的原理及实现
总结:
参考资料
前言
享元模式是一种不常用的设计模式,其主要的目的是为了复用不可变对象,共享内存。
享元模式的原理及实现
享元顾明思议就是被共享的单元,享元模式的意图是为了复用对象,节省内存,前提是享元对象是不可变对象。
具体来讲,当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段)提取出来,设计成享元,让这些大量相似对象引用这些享元。
定义中的“不可变对象”指的是,一旦通过构造函数初始化完成之后,它的状态(对象的成员变量或者属性)就不会再被修改了。所以,不可变对象不能暴露任何 set() 等修改内部状态的方法。之所以要求享元是不可变对象,那是因为它会被多处代码共享使用,避免一处代码对享元进行了修改,影响到其他使用它的代码。
接下来我们介绍一个简单的例子解释下享元模式
假设我们在开发一个棋牌游戏(比如象棋)。一个游戏厅中有成千上万个“房间”,每个房间对应一个棋局。棋局要保存每个棋子的数据,比如:棋子类型(将、相、士、炮等)、棋子颜色(红方、黑方)、棋子在棋局中的位置。利用这些数据,我们就能显示一个完整的棋盘给玩家。具体的代码如下所示。其中,ChessPiece 类表示棋子,ChessBoard 类表示一个棋局,里面保存了象棋中 30 个棋子的信息。
public class ChessPiece {//棋子private int id;private String text;private Color color;private int positionX;private int positionY;public ChessPiece(int id, String text, Color color, int positionX, int positionY) {this.id = id;this.text = text;this.color = color;this.positionX = positionX;this.positionY = positionX;}public static enum Color {RED, BLACK}// ...省略其他属性和getter/setter方法...
}public class ChessBoard {//棋局private Map<Integer, ChessPiece> chessPieces = new HashMap<>();public ChessBoard() {init();}private void init() {chessPieces.put(1, new ChessPiece(1, "車", ChessPiece.Color.BLACK, 0, 0));chessPieces.put(2, new ChessPiece(2,"馬", ChessPiece.Color.BLACK, 0, 1));//...省略摆放其他棋子的代码...}public void move(int chessPieceId, int toPositionX, int toPositionY) {//...省略...}
}
为了记录每个房间当前的棋局情况,我们需要给每个房间都创建一个 ChessBoard 棋局对象。因为游戏大厅中有成千上万的房间(实际上,百万人同时在线的游戏大厅也有很多),那保存这么多棋局对象就会消耗大量的内存。有没有什么办法来节省内存呢?
这个时候,享元模式就可以派上用场了。像刚刚的实现方式,在内存中会有大量的相似对象。这些相似对象的 id、text、color 都是相同的,唯独 positionX、positionY 不同。实际上,我们可以将棋子的 id、text、color 属性拆分出来,设计成独立的类,并且作为享元供多个棋盘复用。这样,棋盘只需要记录每个棋子的位置信息就可以了。具体的代码实现如下所示:
// 享元类
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}// ...省略其他属性和getter方法...
}public class ChessPieceUnitFactory {private static final Map<Integer, ChessPieceUnit> pieces = new HashMap<>();static {pieces.put(1, new ChessPieceUnit(1, "車", ChessPieceUnit.Color.BLACK));pieces.put(2, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK));//...省略摆放其他棋子的代码...}public static ChessPieceUnit getChessPiece(int chessPieceId) {return pieces.get(chessPieceId);}
}public class ChessPiece {private ChessPieceUnit chessPieceUnit;private int positionX;private int positionY;public ChessPiece(ChessPieceUnit unit, int positionX, int positionY) {this.chessPieceUnit = unit;this.positionX = positionX;this.positionY = positionY;}// 省略getter、setter方法
}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(1, new ChessPiece(ChessPieceUnitFactory.getChessPiece(2), 1,0));//...省略摆放其他棋子的代码...}pu