享元模式(FlyWeight Pattern)
概要
记忆关键字:细粒度、共享
定义:运用共享技术有效地支持大量细粒度的对象
类型:结构型
分析:共享对象,将对象的一部分状态(内部状态)设计成可共享的,以减少对象的数量,达到节省内存的目的。
UML类图如下:
一、 涉及的角色
1. 抽象享元类 Flyweight
通常是接口或抽象类,它声明了具体享元类的公共方法。通过这些方法可以向外界提供享元对象的内部状态和设置外部状态。
2. 具体享元类 ConcreteFlyweight
它实现了抽象享元类所声明的方法,其实例称为享元对象,为内部状态提供存储空间。
3. 非共享具体享元类 UnSharedConcreteFlyWeight
并不是所有抽象享元类的子类都需要被共享,不需要被共享的外部状态可设计为非共享具体享元类,它以参数的形式注入到具体享元的相关方法中,可以直接实例化。
4. 享元工厂类 FlyWeightFactory
用于创建和管理享元对象
它针对抽象享元类编程,将各种具体享元对象存储在一个享元池中。当用户请求一个具体享元对象时,享元工厂会检査系统中是否存在符合要求的享元对象,如果存在则提供给客户端,如果不存在,就创建一个新的享元对象。
二、举例
一个游戏厅中有成千上万个“房间”,每个房间对应一个棋局。棋局要保存每个棋子的数据,比如:棋子类型(将、相、士、炮等)、棋子颜色(红方、黑方)、棋子在棋局中的位置。利用这些数据,我们就能显示一个完整的棋盘给玩家。
ChessPiece类表示棋子
ChessBoard类表示一个棋局,里面保存了象棋中30个棋子的信息
为记录每个房间当前的棋局情况,要给每个房间都创建一个ChessBoard棋局对象。因为游戏大厅中有成千上万房间,保存这么多棋局对象就会消耗大量内存。如何节省内存呢?
就得用上享元模式啦。在内存中有大量相似对象。这些相似对象的id、text、color都一样,仅棋子在棋局中的位置:positionX、positionY不同。将棋子的id、text、color属性拆出来,设计成独立类,并且作为享元供多个棋盘复用。棋盘只需记录每个棋子的位置信息
利用工厂类缓存ChessPieceUnit信息(也就是id、text、color)。通过工厂类获取到的ChessPieceUnit就是享元。所有的ChessBoard对象共享这30个ChessPieceUnit对象(因为象棋中只有30个棋子)。在使用享元模式之前,记录1万个棋局,我们要创建30万(30*1万)个棋子的ChessPieceUnit对象。利用享元模式,我们只需要创建30个享元对象供所有棋局共享使用即可,大大节省了内存。
主要通过工厂模式,在工厂类中,通过Map缓存已创建过的享元对象,达到复用。
示例代码如下:
1 // 抽象享元类:棋子单元 2 public abstract class ChessPieceUnit { 3 private int id; 4 private String type; 5 private ChessPieceColor color; 6 7 public ChessPieceUnit(int id, String type, ChessPieceColor color) { 8 this.id = id; 9 this.type = type; 10 this.color = color; 11 } 12 13 public abstract void display(int positionX, int positionY); 14 15 // 省略其他方法和属性的实现 16 17 public int getId() { 18 return id; 19 } 20 21 public String getType() { 22 return type; 23 } 24 25 public ChessPieceColor getColor() { 26 return color; 27 } 28 } 29 30 // 具体享元类:具体棋子单元 31 public class ConcreteChessPieceUnit extends ChessPieceUnit { 32 public ConcreteChessPieceUnit(int id, String type, ChessPieceColor color) { 33 super(id, type, color); 34 } 35 36 @Override 37 public void display(int positionX, int positionY) { 38 System.out.println("棋子:" + getColor() + " " + getType() + ",位置:" + positionX + ", " + positionY); 39 } 40 41 // 省略特有的方法和属性的实现 42 } 43 44 // 享元工厂类:棋子单元工厂 45 public class ChessPieceUnitFactory { 46 private static final Map<Integer, ChessPieceUnit> chessPieces = new HashMap<>(); 47 48 static { 49 chessPieces.put(1, new ConcreteChessPieceUnit(1, "将", ChessPieceColor.RED)); 50 chessPieces.put(2, new ConcreteChessPieceUnit(2, "兵", ChessPieceColor.BLACK)); 51 // 添加其他棋子 52 } 53 54 public static ChessPieceUnit getChessPiece(int chessPieceId) { 55 return chessPieces.get(chessPieceId); 56 } 57 } 58 59 // 客户端代码 60 public class Client { 61 public static void main(String[] args) { 62 ChessPieceUnit chessPiece = ChessPieceUnitFactory.getChessPiece(1); 63 chessPiece.display(0, 0); 64 65 // 其他棋子的使用示例 66 ChessPieceUnit blackPawn = ChessPieceUnitFactory.getChessPiece(2); 67 blackPawn.display(1, 2); 68 } 69 }
在象棋的例子中,如果有一些棋子具有特殊属性,不适合被共享,就可以设计 UnsharedConcreteFlyweight 类。例如,考虑到有些棋子可能有特殊的移动规则,这些规则不适用于所有相同类型和颜色的棋子,那么就可以将这些规则作为 UnsharedConcreteFlyweight 的一部分。
以下是象棋中的 UnsharedConcreteFlyweight 的示例:
1 // 不共享的具体享元对象,包含不被共享的特殊属性 2 public class UnsharedConcreteFlyweight implements ChessPiece { 3 private String type; // 棋子类型,内部状态 4 private String color; // 棋子颜色,内部状态 5 private String specialMove; // 特殊移动规则,不被共享的外部状态 6 7 public UnsharedConcreteFlyweight(String type, String color, String specialMove) { 8 this.type = type; 9 this.color = color; 10 this.specialMove = specialMove; 11 } 12 13 @Override 14 public void display(int x, int y) { 15 System.out.println("Type: " + type + ", Color: " + color + 16 ", Position: (" + x + ", " + y + "), Special Move: " + specialMove); 17 } 18 }
在这个例子中,UnsharedConcreteFlyweight
类包含了额外的特殊移动规则,这些规则是不被共享的外部状态。在实际使用中,可以根据具体的业务需求决定是否需要 UnsharedConcreteFlyweight 类。如果所有棋子都具有相同的属性,可以避免使用不共享的具体享元对象。
三、优缺点分析
1. 优点
- 通过共享对象减少了内存中对象的数量,降低了内存的占用,提高了系统的性能。
- 享元模式的外部状态相对独立,不会影响其内部状态,从而使享元对象可以在不同的环境中被共享。
2. 缺点
- 需要分离出内部状态和外部状态,增加了系统的复杂性。
- 为了使对象可以共享,需要将部分状态外部化,而读取外部状态会使运行时间变长。
3.分析
- 享元模式这样理解:“享”就表示共享,“元”表示对象
- 享元模式的目的在于通过共享尽可能多的相似对象来减小内存占用或计算开销。
- 享元模式中,内部状态和外部状态是两个重要的概念,它们用于描述对象的状态,并在共享对象时进行区分
- 内部状态(Intrinsic State)是可以共享的,它独立于具体的享元对象,因此可以被多个对象共享。内部状态存储在享元对象内部,不会随着外部环境的改变而改变。
- 外部状态 (Extrinsic State)是不可共享的,它取决于具体的应用场景,并且随着外部环境的改变而改变。外部状态由客户端管理,而不是由享元对象管理。
四、使用场景
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。
五、应用实例
1. String常量池
2. 数据库连接池
参考链接:https://zhuanlan.zhihu.com/p/521739369
标签:享元,对象,Pattern,color,棋子,FlyWeight,共享,public From: https://www.cnblogs.com/hld123/p/18001188