享元模式(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