享元模式是一種很常用的思想,核心就是共用,剝離事物的內部狀態與外部狀態,本文對享元模式 FlyWeight進行了簡單介紹,並且給出了該模式的意圖,結構,並且介紹了享元模式的兩種應用方式單純享元模式與複合享元模式,並且給出來了Java的示例代碼。享元模式的特點,結構,使用場景,都可以在本文中找到。 ...
享元模式(FlyWeight) “享”取“共用”之意,“元”取“單元”之意。
意圖
運用共用技術,有效的支持大量細粒度的對象。意圖解析
面向對象的程式設計中,一切皆是對象,這也就意味著系統的運行將會依賴大量的對象。 試想,如果對象的數量過多,勢必會增加系統負擔,導致運行的代價過高。 下麵看兩個小例子理解下 1.)有一首歌曲叫做《大舌頭》 其中有一句歌詞“說說說說 說你愛我 我我我我 說不出口” 如果使用面向對象的編程方式對這段歌詞進行描述,假設一個漢字表示一個對象,你會怎麼做? 你會用七個還是十六個對象進行表示? 2.)有一個文本編輯器軟體,對於每一個字元使用對象進行表示 當打開一篇有很多重覆字元的、數萬字的文章時,你會使用幾個對象進行表示? 如果仍舊採用每個字元占用一個對象,系統勢必崩潰,必然需要共用對象 上面的兩個例子中,都涉及到重覆對象的概念 而享元模式的意圖就是如此,將重覆的對象進行共用以達到支持大量細粒度對象的目的。 如果不進行共用,如例2中描述的那樣,一篇數萬字元的文章將會產生數萬個對象,這將是一場可怕的災難。 flyweight意為輕量級 在我們當前的場景下,寓意為通過共用技術,輕量級的---也就是記憶體占用更小 本質就是“共用”所以中文翻譯過來多稱之為享元 簡言之,享元模式就是要“共用對象” 對於Java語言,我們熟悉的String,就是享元模式的運用 String是不可變對象,一旦創建,將不會改變 在JVM內部,String對象都是共用的 如果一個系統中的兩個String對象,包含的字元串相同,只會創建一個String對象提供給兩個引用 從而實現String對象的共用(new 的對象是兩個不同的) 享元模式又不僅僅是簡單的“共用對象” 上面的兩個小例子中,對於文字中的重覆字元 可以通過共用對象的方式,對某些對象進行共用,從而減少記憶體開銷。 考慮下圖中的情景,這裡面所有的“你”字,到底是不是同樣的?- 是,因為全部都是漢字“你”
- 不是,因為儘管都是漢字“你”,但是他們的字體,顏色,字型大小,卻又明顯不同,所以不是同樣的
小結
享元模式就是為了避免系統中出現大量相同或相似的對象,同時又不影響客戶端程式通過面向對象的方式對這些對象進行操作 享元模式通過共用技術,實現相同或相似對象的重用 比如文編編輯器讀取文本 在邏輯上每一個出現的字元都有一個對象與之對應,然而在物理上它們卻共用同一個享元對象 在享元模式中,存儲這些共用實例對象的地方通常叫做享元池(Flyweight Pool) 享元模式可以結合String的intern()方法一起進行理解 通過區分了內部狀態和外部狀態,就可以將相同內部狀態的對象存儲在池中,池中的對象可以實現共用 需要的時候將對象從池中取出,實現對象的復用 通過向取出的對象註入不同的外部狀態,進而得到一些列相似的對象 而這些看似各異的對象在記憶體中,僅僅存儲了一份,大大節省了空間,所以說很自然的命名為“flyweight”輕量級享元工廠
通過對意圖的認識,可以認為,享元模式其實就是對於“程式中會出現的大量重覆或者相似對象”的一種“重構” 當然,你應該是在設計之初就想到這個問題,而不是真的出現問題後再去真的重構 比如,你想要設計“字元”這種對象時,就應該考慮到他的“大量””重覆““相似”的特點 所以需要分析出字元的內部狀態,與外部狀態 上面也提到對於享元對象,通過享元池進行管理 對於池的管理通常使用工廠模式,藉助於工廠類對享元池進行管理 用戶需要對象時,通過工廠類獲取 工廠提供一個存儲在享元池中的已創建的對象實例,或者創建一個新的實例示例代碼
針對於上面的例子,漢字“你”作為內部狀態,可以進行共用 “顏色”作為外部狀態,由客戶端保存傳遞 創建字元類 Character、漢字字元類ChineseCharacter、顏色類Color以及工廠類CharacterFactory Color含有顏色屬性,通過構造方法設置,getter方法獲取package flyweight; public class Color { public String Color; public Color(String color) { this.Color = color; } public String getColor() { return Color; } }
Character 抽象的字元類,用於描述字元
package flyweight; public abstract class Character { public abstract String getValue(); public void display(Color color) { System.out.println("字元: " + getValue() + " ,顏色: " + color.getColor()); } }漢字字元類,為了簡化,直接設置value為漢字“你”
package flyweight; public class ChineseCharacter extends Character { @Override public String getValue() { return "你"; } }CharacterFactory字元工廠類 通過單例模式創建工廠 內部HashMap用於存儲字元,並且提供獲取方法 為了簡化程式,初始就創建了一個漢字字元“你”存儲於字元中
package flyweight; import java.util.HashMap; public class CharacterFactory { /** * 單例模式 餓漢式創建 */ private static CharacterFactory instance = new CharacterFactory(); /** * 使用HashMap管理享元池 */ private HashMap<String, Object> hm = new HashMap<>(); private CharacterFactory() { Character character = new ChineseCharacter(); hm.put("你", character); } /** * 單例全局訪問介面獲取工廠 */ public static CharacterFactory getInstance() { return instance; } /** * 根據key獲取池中的對象 */ public Character getCharacter(String key) { return (Character) hm.get(key); } }測試代碼 示例中,我們通過工廠,從享元池中獲取了三個漢字字元“你”。 通過 == 可以看得出來,他們都是同一個對象 在分別調用他們的display方法時,在客戶端(此處為我們的Test main方法)中創建,並且傳遞給享元對象 通過方法參數的形式進行外部狀態的設置。 CharacterFactory 單例模式,返回自身實例 CharacterFactory內部維護Character的享元池 Character 依賴Color ChineseCharacter是Character的實現類
結構
將上面的示例轉換為標準的享元模式的名稱 抽象享元角色 FlyWeight 所有具體享元類的超類,為這些類規定了需要實現的公共介面 外部狀態可以通過業務邏輯方法的參數形式傳遞進來 具體享元角色ConcreteFlyWeight 實現抽象享元角色所規定的的介面 需要保存內部狀態,而且,內部狀態必須與外部狀態無關 從而才能使享元對象可以在系統內共用 享元工廠角色 FlyWeightFactory 負責創建和管理享元角色,也就是維護享元池 必須保證享元對象可以被系統適當的共用 接受客戶端的請求 如果有適當符合要求的享元對象,則返回 如果沒有一個適當的享元對象,則創建 客戶端角色Client客戶端角色維護了對所有享元對象的引用 需要保存維護享元對象的外部狀態,然後通過享元對象的業務邏輯方法作為參數形式傳遞
分類
單純享元模式
在上面的結構中,如果所有的ConcreteFlyWeight都可以被共用 也就是所有的FlyWeight子類都可以被共用,那就是所有的享元對象都可以被共用 這種形式被稱之為單純享元模式 單純享元代碼package flyweight.simple; public abstract class FlyWeight { /** * 抽象的業務邏輯方法,接受外部狀態作為參數 */ abstract public void operation(String outerState); }
package flyweight.simple; public class ConcreteFlyWeight extends FlyWeight { private String innerState = null; public ConcreteFlyWeight(String innerState) { this.innerState = innerState; } /** * 外部狀態作為參數傳遞 */ @Override public void operation(String outerState) { System.out.println("innerState = " + innerState + " outerState = " + outerState); } }
package flyweight.simple; import java.util.HashMap; public class FlyWeightFactory { /** * 單例模式 餓漢式創建 */ private static FlyWeightFactory instance = new FlyWeightFactory(); /** * 使用HashMap管理享元池 */ private HashMap<String, Object> hm = new HashMap<>(); private FlyWeightFactory() { } /** * 單例全局訪問介面獲取工廠 */ public static FlyWeightFactory getInstance() { return instance; } /** * 根據innerState獲取池中的對象 * 存在返回,不存在創建並返回 */ public FlyWeight getFylWeight(String innerState) { if(hm.containsKey(innerState)){ return (FlyWeight) hm.get(innerState); }else{ FlyWeight flyWeight = new ConcreteFlyWeight(innerState); hm.put(innerState,flyWeight); return flyWeight; } } }
package flyweight.simple; public class Test { public static void main(String[] args){ FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance(); FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First"); FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second"); FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First"); System.out.println(flyWeight1); System.out.println(flyWeight2); System.out.println(flyWeight3); System.out.println(); flyWeight1.operation("outer state XXX"); flyWeight2.operation("outer state YYY"); flyWeight3.operation("outer state ZZZ"); } }
複合享元模式
與單純享元模式對應的是複合享元模式 單純享元模式中,所有的享元對象都可以共用 複合享元模式中,則並不是所有的ConcreteFlyWeight都可以被共用 也就是說:不是所有的享元對象都可以被共用 實際上,並不是所有的FlyWeight子類都需要被共用 FlyWeight介面使的可以進行共用,但是沒有任何必要 強制必須共用 實踐中,UnsharedConcreteFlyWeight對象通常將ConcreteFlyWeight對象作為子節點 與單純享元模式相比,僅僅是擁有了不可共用的具體子類 而且,這個子類往往是應用了組合模式,將ConcreteFlyWeight對象作為子節點 複合享元角色UnsharedConcreteFlyWeight複合享元角色,也就是不可共用的,也被稱為 不可共用的享元對象
但是一個複合享元對象可以分解為多個本身是單純享元對象的組合
這些單純的享元對象就又是可以共用的 複合享元代碼 將簡單模式中的示例代碼進行改造 FlyWeight不變
package flyweight.composite; public abstract class FlyWeight { /** * 抽象的業務邏輯方法,接受外部狀態作為參數 */ abstract public void operation(String outerState); }ConcreteFlyWeight不變
package flyweight.composite; public class ConcreteFlyWeight extends FlyWeight { private String innerState = null; public ConcreteFlyWeight(String innerState) { this.innerState = innerState; } /** * 外部狀態作為參數傳遞 */ @Override public void operation(String outerState) { System.out.println("innerState = " + innerState + " outerState = " + outerState); } }新增加不共用的子類也就是組合的享元子類 內部使用list 維護單純享元模式對象,提供add方法進行添加 提供operation操作
package flyweight.composite; import java.util.ArrayList; import java.util.List; public class UnsharedConcreateFlyWeight extends FlyWeight { private String innerState = null; public UnsharedConcreateFlyWeight(String innerState) { this.innerState = innerState; } private List<FlyWeight> list = new ArrayList<>(); public void add(FlyWeight flyWeight) { list.add(flyWeight); } @Override public void operation(String outerState) { for (FlyWeight flyWeight:list) { flyWeight.operation(outerState); } } }FlyWeightFactory工廠類進行改造 新增加public UnsharedConcreateFlyWeight getCompositeFylWeight(String state) 用於獲得組合享元對象
package flyweight.composite; import java.util.HashMap; public class FlyWeightFactory { /** * 單例模式 餓漢式創建 */ private static FlyWeightFactory instance = new FlyWeightFactory(); /** * 使用HashMap管理享元池 */ private HashMap<String, Object> hm = new HashMap<>(); /** * 管理複合享元對象 */ private HashMap<String, Object> compositeHm = new HashMap<>(); private FlyWeightFactory() { } /** * 單例全局訪問介面獲取工廠 */ public static FlyWeightFactory getInstance() { return instance; } /** * 根據innerState獲取池中的對象 * 存在返回,不存在創建並返回 */ public FlyWeight getFylWeight(String innerState) { if(hm.containsKey(innerState)){ return (FlyWeight) hm.get(innerState); }else{ FlyWeight flyWeight = new ConcreteFlyWeight(innerState); hm.put(innerState,flyWeight); return flyWeight; } } /** * 根據innerState獲取池中的對象 * 存在返回,不存在創建並返回 */ public UnsharedConcreateFlyWeight getCompositeFylWeight(String state) { if(compositeHm.containsKey(state)){ return (UnsharedConcreateFlyWeight) compositeHm.get(state); }else{ UnsharedConcreateFlyWeight flyWeight = new UnsharedConcreateFlyWeight(state); compositeHm.put(state,flyWeight); return flyWeight; } } }測試類也進行改造
package flyweight.composite; public class Test { public static void main(String[] args){ FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance(); FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First"); FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second"); FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First"); System.out.println(flyWeight1); System.out.println(flyWeight2); System.out.println(flyWeight3); System.out.println("###########################################"); flyWeight1.operation("outer state XXX"); flyWeight2.operation("outer state YYY"); flyWeight3.operation("outer state ZZZ"); System.out.println("###########################################"); UnsharedConcreateFlyWeight compositeFlyWeight = flyWeightFactory.getCompositeFylWeight("composite"); compositeFlyWeight.add(flyWeight1); compositeFlyWeight.add(flyWeight2); compositeFlyWeight.operation("composite out state OOO"); } }測試程式在原來的基礎上,新獲得了一個組合享元對象 然後將兩個單純享元對象添加到組合享元對象中 然後調用operation,通過列印信息可以看得出來 不同的單純享元對象,他們卻有了一致的外部狀態 所以使用複合享元模式的一個常用目的就是: 多個內部狀態不同的單純享元對象,擁有一致的外部狀態 這種場景下,就可以考慮使用複合享元模式
使用場景
如果有下列情況,則可以考慮使用享元模式- 應用程式中使用了大量的對象
- 大量的對象明顯增加了程式的存儲運行開銷
- 對象可以提取出內部狀態,並且可以分離外部狀態