享元模式
定義:共享元對象,運用共享技術有效地支持大量細粒度對象的復用。如果在一個系統中存在多個相同的對象,那么只需要共享一份對象的拷貝,而不必為每一次使用創建新的對象。
享元模式是為數不多的、只為提升系統性能而生的設計模式,主要作用就是復用大對象(重量級對象),以節省內存空間和對象創建時間。
面向對象可以非常方便的解決一些擴展性的問題,但是在這個過程中系統務必會產生一些類或者對象,如果系統中存在對象的個數過多時,將會導致系統的性能下降。對於這樣的問題解決最簡單直接的辦法就是減少系統中對象的個數。享元模式提供了一種解決方案,使用共享技術實現相同或者相似對象的重用。也就是說實現相同或者相似對象的代碼共享。
所謂享元模式就是運行共享技術有效地支持大量細粒度對象的復用。系統使用少量對象,而且這些都比較相似,狀態變化小,可以實現對象的多次復用。
共享模式是支持大量細粒度對象的復用,所以享元模式要求能夠共享的對象必須是細粒度對象。
首先了解兩個概念:內部狀態、外部狀態。
內部狀態:在享元對象內部不隨外界環境改變而改變的共享部分。
外部狀態:隨着環境的改變而改變,不能夠共享的狀態就是外部狀態。
由於享元模式區分了內部狀態和外部狀態,所以我們可以通過設置不同的外部狀態使得相同的對象可以具備一些不同的特性,而內部狀態設置為相同部分。在我們的程序設計過程中,我們可能會需要大量的細粒度對象來表示對象,如果這些對象除了幾個參數不同外其他部分都相同,這個時候我們就可以利用享元模式來大大減少應用程序當中的對象。如何利用享元模式呢?這里我們只需要將他們少部分的不同的部分當做參數移動到類實例的外部,然后在方法調用的時候將他們傳遞過來就可以了。這里也就說明了一點:內部狀態存儲於享元對象內部,而外部狀態則應該由客戶端來考慮。
二、 模式結構
下圖是享元模式的UML結構圖
享元模式存在如下幾個角色:
模式結構
-
Flyweight: 享元接口,所有具體享元類的超類或接口,通過該接口Flyweight可以接受並作用於外部狀態。通過該接口可以傳入外部的狀態,在享元對象的方法處理中可能會使用這些外部的數據。
-
ConcreteFlyweight: 具體的享元實現對象,指定內部狀態,必須是共享的,需要封裝Flyweight的內部狀態。
-
UnshareConcreteFlyweight: 非共享的享元實現對象,並不是所有的Flyweight實現對象都需要共享。非共享的享元實現對象通常是對享元對象的組合對象。
-
FlyweightFactoty: 享元工廠類,主要用來創建並管理共享的享元對象,並對外提供訪問共享享元的接口。當用戶請求一個Flyweight時,FlyweightFactory就會提供一個已經創建的Flyweight對象或者新建一個(如果不存在)。
-
Client: 享元客戶端,主要的工作就是維持一個對Flyweight的引用,計算或存儲享元的外部狀態,當然這里可訪問共享和不共享的Flyweight對象。
享元模式的核心在於享元工廠類,享元工廠類的作用在於提供一個用於存儲享元對象的享元池,用戶需要對象時,首先從享元池中獲取,如果享元池中不存在,則創建一個新的享元對象返回給用戶,並在享元池中保存該新增對象。
三、 模式實現
1、單純享元模式:在單純享元模式中,所有的享元對象都是可以共享的,即所有抽象享元類的子類都可共享,不存在非共享具體享元類。
1 //抽象享元角色類 2 public interface Flyweight { 3 //一個示意性方法,參數state是外蘊狀態 4 public void operation(String state); 5 } 6 7 //具體享元角色類 8 //具體享元角色類ConcreteFlyweight有一個內蘊狀態,在本例中一個Character類型的intrinsicState屬性代表,它的值應當在享元對象 9 //被創建時賦予。所有的內蘊狀態在對象創建之后,就不會再改變了。如果一個享元對象有外蘊狀態的話,所有的外部狀態都必須存儲在客戶端, 10 //在使用享元對象時,再由客戶端傳入享元對象。這里只有一個外蘊狀態,operation()方法的參數state就是由外部傳入的外蘊狀態。 11 public class ConcreteFlyweight implements Flyweight { 12 private Character intrinsicState = null; 13 /** 14 * 構造函數,內蘊狀態作為參數傳入 15 * @param state 16 */ 17 public ConcreteFlyweight(Character state){ 18 this.intrinsicState = state; 19 } 20 21 22 /** 23 * 外蘊狀態作為參數傳入方法中,改變方法的行為, 24 * 但是並不改變對象的內蘊狀態。 25 */ 26 @Override 27 public void operation(String state) { 28 // TODO Auto-generated method stub 29 System.out.println("Intrinsic State = " + this.intrinsicState); 30 System.out.println("Extrinsic State = " + state); 31 } 32 33 } 34 35 36 //享元工廠角色類 37 //享元工廠角色類,必須指出的是,客戶端不可以直接將具體享元類實例化,而必須通過一個工廠對象,利用一個factory()方法得到享元對象。 38 //一般而言,享元工廠對象在整個系統中只有一個,因此也可以使用單例模式。 39 40 //當客戶端需要單純享元對象的時候,需要調用享元工廠的factory()方法,並傳入所需的單純享元對象的內蘊狀態,由工廠方法產生所需要的 41 //享元對象。 42 public class FlyweightFactory { 43 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); 44 45 public Flyweight factory(Character state){ 46 //先從緩存中查找對象 47 Flyweight fly = files.get(state); 48 if(fly == null){ 49 //如果對象不存在則創建一個新的Flyweight對象 50 fly = new ConcreteFlyweight(state); 51 //把這個新的Flyweight對象添加到緩存中 52 files.put(state, fly); 53 } 54 return fly; 55 } 56 } 57 58 59 //客戶端類 60 public class Client { 61 62 public static void main(String[] args) { 63 // TODO Auto-generated method stub 64 FlyweightFactory factory = new FlyweightFactory(); 65 Flyweight fly = factory.factory(new Character('a')); 66 fly.operation("First Call"); 67 68 fly = factory.factory(new Character('b')); 69 fly.operation("Second Call"); 70 71 fly = factory.factory(new Character('a')); 72 fly.operation("Third Call"); 73 } 74 75 }
雖然客戶端申請了三個享元對象,但是實際創建的享元對象只有兩個,這就是共享的含義。運行結果如下:
2、復合享元模式:將一些單純享元使用組合模式加以組合,可以形成復合享元對象,這樣的復合享元對象本身不能共享,但是它們可以分解成單純享元對象,而后者則可以共享。
實現示例:
1 //抽象享元角色類 2 public interface Flyweight { 3 //一個示意性方法,參數state是外蘊狀態 4 public void operation(String state); 5 } 6 7 8 //具體享元角色類 9 //具體享元角色類ConcreteFlyweight有一個內蘊狀態,在本例中一個Character類型的intrinsicState屬性代表,它的值應當在享元對象 10 //被創建時賦予。所有的內蘊狀態在對象創建之后,就不會再改變了。如果一個享元對象有外蘊狀態的話,所有的外部狀態都必須存儲在客戶端, 11 //在使用享元對象時,再由客戶端傳入享元對象。這里只有一個外蘊狀態,operation()方法的參數state就是由外部傳入的外蘊狀態。 12 public class ConcreteFlyweight implements Flyweight { 13 private Character intrinsicState = null; 14 /** 15 * 構造函數,內蘊狀態作為參數傳入 16 * @param state 17 */ 18 public ConcreteFlyweight(Character state){ 19 this.intrinsicState = state; 20 } 21 22 23 /** 24 * 外蘊狀態作為參數傳入方法中,改變方法的行為, 25 * 但是並不改變對象的內蘊狀態。 26 */ 27 @Override 28 public void operation(String state) { 29 // TODO Auto-generated method stub 30 System.out.println("Intrinsic State = " + this.intrinsicState); 31 System.out.println("Extrinsic State = " + state); 32 } 33 34 } 35 36 37 //復合享元角色類 38 //復合享元對象是由單純享元對象通過復合而成的,因此它提供了add()這樣的聚集管理方法。由於一個復合享元對象具有不同的聚集元素, 39 //這些聚集元素在復合享元對象被創建之后加入,這本身就意味着復合享元對象的狀態是會改變的,因此復合享元對象是不能共享的。 40 //復合享元角色實現了抽象享元角色所規定的接口,也就是operation()方法,這個方法有一個參數,代表復合享元對象的外蘊狀態。 41 //一個復合享元對象的所有單純享元對象元素的外蘊狀態都是與復合享元對象的外蘊狀態相等的; 42 //而一個復合享元對象所含有的單純享元對象的內蘊狀態一般是不相等的,不然就沒有使用價值了。 43 public class ConcreteCompositeFlyweight implements Flyweight { 44 45 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); 46 /** 47 * 增加一個新的單純享元對象到聚集中 48 */ 49 public void add(Character key , Flyweight fly){ 50 files.put(key,fly); 51 } 52 /** 53 * 外蘊狀態作為參數傳入到方法中 54 */ 55 @Override 56 public void operation(String state) { 57 Flyweight fly = null; 58 for(Object o : files.keySet()){ 59 fly = files.get(o); 60 fly.operation(state); 61 } 62 63 } 64 65 } 66 67 68 //享元工廠角色類 69 //享元工廠角色提供兩種不同的方法,一種用於提供單純享元對象,另一種用於提供復合享元對象。 70 public class FlyweightFactory { 71 private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>(); 72 /** 73 * 復合享元工廠方法 74 */ 75 public Flyweight factory(List<Character> compositeState){ 76 ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight(); 77 78 for(Character state : compositeState){ 79 compositeFly.add(state,this.factory(state)); 80 } 81 82 return compositeFly; 83 } 84 /** 85 * 單純享元工廠方法 86 */ 87 public Flyweight factory(Character state){ 88 //先從緩存中查找對象 89 Flyweight fly = files.get(state); 90 if(fly == null){ 91 //如果對象不存在則創建一個新的Flyweight對象 92 fly = new ConcreteFlyweight(state); 93 //把這個新的Flyweight對象添加到緩存中 94 files.put(state, fly); 95 } 96 return fly; 97 } 98 } 99 100 101 //客戶端類 102 public class Client { 103 104 public static void main(String[] args) { 105 List<Character> compositeState = new ArrayList<Character>(); 106 compositeState.add('a'); 107 compositeState.add('b'); 108 compositeState.add('c'); 109 compositeState.add('a'); 110 compositeState.add('b'); 111 112 FlyweightFactory flyFactory = new FlyweightFactory(); 113 Flyweight compositeFly1 = flyFactory.factory(compositeState); 114 Flyweight compositeFly2 = flyFactory.factory(compositeState); 115 compositeFly1.operation("Composite Call"); 116 117 System.out.println("---------------------------------"); 118 System.out.println("復合享元模式是否可以共享對象:" + (compositeFly1 == compositeFly2)); 119 120 Character state = 'a'; 121 Flyweight fly1 = flyFactory.factory(state); 122 Flyweight fly2 = flyFactory.factory(state); 123 System.out.println("單純享元模式是否可以共享對象:" + (fly1 == fly2)); 124 } 125 }
運行結果如下:
四、 模式優缺點
享元模式是一個 考慮系統性能的設計模式,通過使用享元模式可以節約內存空間,提高系統的性能。
優點
1、享元模式的優點在於它能夠極大的減少系統中對象的個數。
2、享元模式由於使用了外部狀態,外部狀態相對獨立,不會影響到內部狀態,所以享元模式使得享元對象能夠在不同的環境被共享。
缺點
1、由於享元模式需要區分外部狀態和內部狀態,使得應用程序在某種程度上來說更加復雜化了。
2、為了使對象可以共享,享元模式需要將享元對象的狀態外部化,而讀取外部狀態使得運行時間變長。
五、 模式適用場景
JDK 里的享元模式:在 JDK 的設計里,也有很享元模式,如一些常量池的設計(String 常量池、Integer 常量池等等);
1、如果一個系統中存在大量的相同或者相似的對象,由於這類對象的大量使用,會造成系統內存的耗費,可以使用享元模式來減少系統中對象的數量。
2、對象的大部分狀態都可以外部化,可以將這些外部狀態傳入對象中。
六、 模式總結
1、享元模式可以極大地減少系統中對象的數量。但是它可能會引起系統的邏輯更加復雜化。
2、享元模式的核心在於享元工廠,它主要用來確保合理地共享享元對象。
3、內部狀態為不變共享部分,存儲於享元享元對象內部,而外部狀態是可變部分,它應當油客戶端來負責。