標籤:
Flyweight在拳擊比賽中指最輕量級,即“蠅量級”或“雨量級”,這裡選擇使用“享元模式”的意譯,是因為這樣更能反映模式的用意。享元模式是對象的結構模式。享元模式以共用的方式高效地支援大量的細粒度對象。
Java中的String類型
在JAVA語言中,String類型就是使用了享元模式。String對象是final類型,對象一旦建立就不可改變。在JAVA中字串常量都是存在常量池中的,JAVA會確保一個字串常量在常量池中只有一個拷貝。String a="abc",其中"abc"就是一個字串常量。
public class Test { public static void main(String[] args) { String a = "abc"; String b = "abc"; System.out.println(a==b); }}
上面的例子中結果為:true ,這就說明a和b兩個引用都指向了常量池中的同一個字串常量"abc"。這樣的設計避免了在建立N多相同對象時所產生的不必要的大量的資源消耗。
享元模式的結構
享元模式採用一個共用來避免大量擁有相同內容對象的開銷。這種開銷最常見、最直觀的就是記憶體的損耗。享元對象能做到共用的關鍵是區分內蘊狀態(Internal State)和外蘊狀態(External State)。
一個內蘊狀態是儲存在享元對象內部的,並且是不會隨環境的改變而有所不同。因此,一個享元可以具有內蘊狀態並可以共用。
一個外蘊狀態是隨環境的改變而改變的、不可以共用的。享元對象的外蘊狀態必須由用戶端儲存,並在享元對象被建立之後,在需要使用的時候再傳入到享元對象內部。外蘊狀態不可以影響享元對象的內蘊狀態,它們是相互獨立的。
享元模式可以分成單純享元模式和複合享元模式兩種形式。
單純享元模式
在單純的享元模式中,所有的享元對象都是可以共用的。
單純享元模式所涉及到的角色如下:
● 抽象享元(Flyweight)角色 :給出一個抽象介面,以規定出所有具體享元角色需要實現的方法。
● 具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的介面。如果有內蘊狀態的話,必須負責為內蘊狀態提供儲存空間。
● 享元工廠(FlyweightFactory)角色 :本角色負責建立和管理享元角色。本角色必須保證享元對象可以被系統適當地共用。當一個用戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當建立一個合適的享元對象。
原始碼
抽象享元角色類
public interface Flyweight { //一個示意性方法,參數state是外蘊狀態 public void operation(String state);}
具體享元角色類ConcreteFlyweight有一個內蘊狀態,在本例中一個Character類型的intrinsicState屬性代表,它的值應當在享元對象被建立時賦予。所有的內蘊狀態在對象建立之後,就不會再改變了。
如果一個享元對象有外蘊狀態的話,所有的外部狀態都必須儲存在用戶端,在使用享元對象時,再由用戶端傳入享元對象。這裡只有一個外蘊狀態,operation()方法的參數state就是由外部傳入的外蘊狀態。
public class ConcreteFlyweight implements Flyweight { private Character intrinsicState = null; /** * 建構函式,內蘊狀態作為參數傳入 * @param state */ public ConcreteFlyweight(Character state){ this.intrinsicState = state; } /** * 外蘊狀態作為參數傳入方法中,改變方法的行為, * 但是並不改變對象的內蘊狀態。 */ @Override public void operation(String state) { // TODO Auto-generated method stub System.out.println("Intrinsic State = " + this.intrinsicState); System.out.println("Extrinsic State = " + state); }}
享元工廠角色類,必須指出的是,用戶端不可以直接將具體享元類執行個體化,而必須通過一個工廠對象,利用一個factory()方法得到享元對象。一般而言,享元工廠對象在整個系統中只有一個,因此也可以使用單例模式。
當用戶端需要單純享元對象的時候,需要調用享元工廠的factory()方法,並傳入所需的單純享元對象的內蘊狀態,由Factory 方法產生所需要的享元對象。
public class FlyweightFactory { private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); public Flyweight factory(Character state){ //先從緩衝中尋找對象 Flyweight fly = files.get(state); if(fly == null){ //如果對象不存在則建立一個新的Flyweight對象 fly = new ConcreteFlyweight(state); //把這個新的Flyweight對象添加到緩衝中 files.put(state, fly); } return fly; }}
用戶端類
public class Client { public static void main(String[] args) { // TODO Auto-generated method stub FlyweightFactory factory = new FlyweightFactory(); Flyweight fly = factory.factory(new Character('a')); fly.operation("First Call"); fly = factory.factory(new Character('b')); fly.operation("Second Call"); fly = factory.factory(new Character('a')); fly.operation("Third Call"); }}
雖然用戶端申請了三個享元對象,但是實際建立的享元對象只有兩個,這就是共用的含義。運行結果如下:
複合享元模式
在單純享元模式中,所有的享元對象都是單純享元對象,也就是說都是可以直接共用的。還有一種較為複雜的情況,將一些單純享元使用合成模式加以複合,形成複合享元對象。這樣的複合享元對象本身不能共用,但是它們可以分解成單純享元對象,而後者則可以共用。
複合享元角色所涉及到的角色如下:
● 抽象享元(Flyweight)角色 :給出一個抽象介面,以規定出所有具體享元角色需要實現的方法。
● 具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的介面。如果有內蘊狀態的話,必須負責為內蘊狀態提供儲存空間。
● 複合享元(ConcreteCompositeFlyweight)角色 :複合享元角色所代表的對象是不可以共用的,但是一個複合享元對象可以分解成為多個本身是單純享元對象的組合。複合享元角色又稱作不可共用的享元對象。
● 享元工廠(FlyweightFactory)角色 :本角 色負責建立和管理享元角色。本角色必須保證享元對象可以被系統適當地共用。當一個用戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有 一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當建立一個 合適的享元對象。
原始碼
抽象享元角色類
public interface Flyweight { //一個示意性方法,參數state是外蘊狀態 public void operation(String state);}
具體享元角色類
public class ConcreteFlyweight implements Flyweight { private Character intrinsicState = null; /** * 建構函式,內蘊狀態作為參數傳入 * @param state */ public ConcreteFlyweight(Character state){ this.intrinsicState = state; } /** * 外蘊狀態作為參數傳入方法中,改變方法的行為, * 但是並不改變對象的內蘊狀態。 */ @Override public void operation(String state) { // TODO Auto-generated method stub System.out.println("Intrinsic State = " + this.intrinsicState); System.out.println("Extrinsic State = " + state); }}
複合享元對象是由單純享元對象通過複合而成的,因此它提供了add()這樣的聚集管理方法。由於一個複合享元對象具有不同的聚集元素,這些聚集元素在複合享元對象被建立之後加入,這本身就意味著複合享元對象的狀態是會改變的,因此複合享元對象是不能共用的。
複合享元角色實現了抽象享元角色所規定的介面,也就是operation()方法,這個方法有一個參數,代表複合享元對象的外蘊狀態。一個複合享元對象的所有單純享元對象元素的外蘊狀態都是與複合享元對象的外蘊狀態相等的;而一個複合享元對象所含有的單純享元對象的內蘊狀態一般是不相等的,不然就沒有使用價值了。
public class ConcreteCompositeFlyweight implements Flyweight { private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); /** * 增加一個新的單純享元對象到聚集中 */ public void add(Character key , Flyweight fly){ files.put(key,fly); } /** * 外蘊狀態作為參數傳入到方法中 */ @Override public void operation(String state) { Flyweight fly = null; for(Object o : files.keySet()){ fly = files.get(o); fly.operation(state); } }}
享元工廠角色提供兩種不同的方法,一種用於提供單純享元對象,另一種用於提供複合享元對象。
public class FlyweightFactory { private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); /** * 複合享元Factory 方法 */ public Flyweight factory(List<Character> compositeState){ ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight(); for(Character state : compositeState){ compositeFly.add(state,this.factory(state)); } return compositeFly; } /** * 單純享元Factory 方法 */ public Flyweight factory(Character state){ //先從緩衝中尋找對象 Flyweight fly = files.get(state); if(fly == null){ //如果對象不存在則建立一個新的Flyweight對象 fly = new ConcreteFlyweight(state); //把這個新的Flyweight對象添加到緩衝中 files.put(state, fly); } return fly; }}
用戶端角色
public class Client { public static void main(String[] args) { List<Character> compositeState = new ArrayList<Character>(); compositeState.add(‘a‘); compositeState.add(‘b‘); compositeState.add(‘c‘); compositeState.add(‘a‘); compositeState.add(‘b‘); FlyweightFactory flyFactory = new FlyweightFactory(); Flyweight compositeFly1 = flyFactory.factory(compositeState); Flyweight compositeFly2 = flyFactory.factory(compositeState); compositeFly1.operation("Composite Call"); System.out.println("---------------------------------"); System.out.println("複合享元模式是否可以共用對象:" + (compositeFly1 == compositeFly2)); Character state = ‘a‘; Flyweight fly1 = flyFactory.factory(state); Flyweight fly2 = flyFactory.factory(state); System.out.println("單純享元模式是否可以共用對象:" + (fly1 == fly2)); }}
運行結果如下:
從運行結果可以看出,一個複合享元對象的所有單純享元對象元素的外蘊狀態都是與複合享元對象的外蘊狀態相等的。即外運狀態都等於Composite Call。
從運行結果可以看出,一個複合享元對象所含有的單純享元對象的內蘊狀態一般是不相等的。即內蘊狀態分別為b、c、a。
從運行結果可以看出,複合享元對象是不能共用的。即使用相同的對象compositeState通過工廠分別兩次建立出的對象不是同一個對象。
從運行結果可以看出,單純享元對象是可以共用的。即使用相同的對象state通過工廠分別兩次建立出的對象是同一個對象。
到此,我們先簡單總結下何種情況下可以考慮使用享元模式
1、一個系統中存在著大量的細粒度對象;
2、這些細粒度對象耗費了大量的記憶體。
3、這些細粒度對象的狀態中的大部分都可以外部化
// 介面interface FlyWeight { public void operation(String s);}// 具體實作類別class ConcreteFlyWeight implements FlyWeight { private String str;// 內蘊狀態 public ConcreteFlyWeight(String str) { this.str = str; } public void operation(String s) { System.out.println("內蘊變數:" + str); System.out.println("外蘊變數:" + s); }}// 享元工廠class FlyWeightFactory { public FlyWeightFactory() { } private Hashtable<String, ConcreteFlyWeight> flyWeights = new Hashtable<String, ConcreteFlyWeight>(); public ConcreteFlyWeight factory(String str) { ConcreteFlyWeight flyWeight; flyWeight = flyWeights.get(str); if (null == flyWeight) { flyWeight = new ConcreteFlyWeight(str); flyWeights.put(str, flyWeight); } return flyWeight; } public int getFlyWeightSize() { return flyWeights.size(); }}
執行代碼
FlyWeightFactory factory = new FlyWeightFactory();FlyWeight flyWeight = factory.factory("a");FlyWeight flyWeight2 = factory.factory("b");FlyWeight flyWeight3 = factory.factory("a");flyWeight.operation("a fly weight");flyWeight2.operation("b fly weight");flyWeight3.operation("c fly weight");System.out.println(flyWeight == flyWeight3);System.out.println(factory.getFlyWeightSize());
執行結果
內蘊變數:a
外蘊變數:a fly weight
內蘊變數:b
外蘊變數:b fly weight
內蘊變數:a
外蘊變數:c fly weight
true
2
結論:本例子享元模式中,享元工廠只有2個對象,外部可以共用他們,並且內蘊變數不會受到影響
整理下模式架構代碼
● 抽象享元(Flyweight)角色 :給出一個抽象介面,以規定出所有具體享元角色需要實現的方法。
● 具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的介面。如果有內蘊狀態的話,必須負責為內蘊狀態提供儲存空間。
● 享元工廠(FlyweightFactory)角色 :本角色負責建立和管理享元角色。本角色必須保證享元對象可以被系統適當地共用。當一個用戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當建立一個合適的享元對象。
我們會發現,享元模式其實是簡單工廠的封裝,當然實際情境是不一樣的
複合享元模式
上述說的其實是單純的享元,享元模式還有一種複雜的情況,就是複合享元。
在單純享元模式中,所有的享元對象都是單純享元對象,也就是說都是可以直接共用的
將一些單純享元使用合成模式加以複合,形成複合享元對象。這樣的複合享元對象本身不能共用,但是它們可以分解成單純享元對象,而後者則可以共用。
我們看下個例子
CompositeFlyWeigthFactory factory2=new CompositeFlyWeigthFactory(); List<String> ls=Arrays.asList(new String[]{"a","b","c","a","d"}); FlyWeight compositeFly=factory2.factory(ls); FlyWeight compositeFly2=factory2.factory(ls); System.out.println("---------------compositeFly-----------------"); compositeFly.operation("a composite fly weight"); System.out.println("---------------compositeFly2-----------------"); compositeFly2.operation("a composite fly weight"); System.out.println(compositeFly==compositeFly2); System.out.println(factory2.factory("a")==factory2.factory("a"));
執行結果
---------------c1-----------------
內蘊變數:荔枝肉
外蘊變數:c1的菜單譜
內蘊變數:福跳牆
外蘊變數:c1的菜單譜
---------------c2-----------------
內蘊變數:鍋邊糊
外蘊變數:c2的菜單普
內蘊變數:荔枝肉
外蘊變數:c2的菜單普
享元模式的優缺點
享元模式的優點在於它大幅度地降低記憶體中對象的數量。但是,它做到這一點所付出的代價也是很高的:
● 享元模式使得系統更加複雜。為了使對象可以共用,需要將一些狀態外部化,這使得程式的邏輯複雜化。
● 享元模式將享元對象的狀態外部化,而讀取外部狀態使得已耗用時間稍微變長。
本文借鑒:http://www.cnblogs.com/java-my-life/archive/2012/04/26/2468499.html
http://www.cnblogs.com/draem0507/p/3793243.html
著作權聲明:歡迎轉載,希望在你轉載的同時,添加原文地址,謝謝配合
《Java設計模式》之享元模式