在軟件開發過程,若是咱們須要重複使用某個對象的時候,若是咱們重複地使用new建立這個對象的話,這樣咱們在內存就須要屢次地去申請內存空間了,這樣可能會出現內存使用愈來愈多的狀況,這樣的問題是很是嚴重,然而享元模式能夠解決這個問題,下面具體看看享元模式是如何去解決這個問題的。html
在前面說了,享元模式能夠解決上面的問題了,在介紹享元模式以前,讓咱們先要分析下若是去解決上面那個問題,上面的問題就是重複建立了同一個對象,若是讓咱們去解決這個問題確定會這樣想:「既然都是同一個對象,能不能只建立一個對象,而後下次須要建立這個對象的時候,讓它直接用已經建立好了的對象就行了」,也就是說——讓一個對象共享。不錯,這個也是享元模式的實現精髓所在。c#
介紹完享元模式的精髓以後,讓咱們具體看看享元模式的正式定義:編輯器
享元模式——運用共享技術有效地支持大量細粒度的對象。享元模式能夠避免大量類似類的開銷,在軟件開發中若是須要生成大量細粒度的類實例來表示數據,若是這些實例除了幾個參數外基本上都是相同的,這時候就可使用享元模式來大幅度減小須要實例化類的數量。若是能把這些參數(指的這些類實例不一樣的參數)移動類實例外面,在方法調用時將他們傳遞進來,這樣就能夠經過共享大幅度地減小單個實例的數目。(這個也是享元模式的實現要領),然而咱們把類實例外面的參數稱爲享元對象的外部狀態,把在享元對象內部定義稱爲內部狀態。具體享元對象的內部狀態與外部狀態的定義爲:ide
內部狀態:在享元對象的內部而且不會隨着環境的改變而改變的共享部分函數
外部狀態:隨環境改變而改變的,不能夠共享的狀態。性能
分析完享元模式的實現思路以後,相信你們實現享元模式確定沒什麼問題了,下面以一個世紀的應用來實現下享元模式。這個例子是:一個文本編輯器中會出現不少字面,使用享元模式去實現這個文本編輯器的話,會把每一個字面作成一個享元對象。享元對象的內部狀態就是這個字面,而字母在文本中的位置和字體風格等其餘信息就是它的外部狀態。下面就以這個例子來實現下享元模式,具體實現代碼以下:字體
/// <summary> /// 客戶端調用 /// </summary> class Client { static void Main(string[] args) { // 定義外部狀態,例如字母的位置等信息 int externalstate = 10; // 初始化享元工廠 FlyweightFactory factory = new FlyweightFactory(); // 判斷是否已經建立了字母A,若是已經建立就直接使用建立的對象A Flyweight fa = factory.GetFlyweight("A"); if (fa != null) { // 把外部狀態做爲享元對象的方法調用參數 fa.Operation(--externalstate); } // 判斷是否已經建立了字母B Flyweight fb = factory.GetFlyweight("B"); if (fb != null) { fb.Operation(--externalstate); } // 判斷是否已經建立了字母C Flyweight fc = factory.GetFlyweight("C"); if (fc != null) { fc.Operation(--externalstate); } // 判斷是否已經建立了字母D Flyweight fd= factory.GetFlyweight("D"); if (fd != null) { fd.Operation(--externalstate); } else { Console.WriteLine("駐留池中不存在字符串D"); // 這時候就須要建立一個對象並放入駐留池中 ConcreteFlyweight d = new ConcreteFlyweight("D"); factory.flyweights.Add("D", d); } Console.Read(); } } /// <summary> /// 享元工廠,負責建立和管理享元對象 /// </summary> public class FlyweightFactory { // 最好使用泛型Dictionary<string,Flyweighy> //public Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>(); public Hashtable flyweights = new Hashtable(); public FlyweightFactory() { flyweights.Add("A", new ConcreteFlyweight("A")); flyweights.Add("B", new ConcreteFlyweight("B")); flyweights.Add("C", new ConcreteFlyweight("C")); } public Flyweight GetFlyweight(string key) { // 更好的實現以下 //Flyweight flyweight = flyweights[key] as Flyweight; //if (flyweight == null) //{ // Console.WriteLine("駐留池中不存在字符串" + key); // flyweight = new ConcreteFlyweight(key); //} //return flyweight; return flyweights[key] as Flyweight; } } /// <summary> /// 抽象享元類,提供具體享元類具備的方法 /// </summary> public abstract class Flyweight { public abstract void Operation(int extrinsicstate); } // 具體的享元對象,這樣咱們不把每一個字母設計成一個單獨的類了,而是做爲把共享的字母做爲享元對象的內部狀態 public class ConcreteFlyweight : Flyweight { // 內部狀態 private string intrinsicstate ; // 構造函數 public ConcreteFlyweight(string innerState) { this.intrinsicstate = innerState; } /// <summary> /// 享元類的實例方法 /// </summary> /// <param name="extrinsicstate">外部狀態</param> public override void Operation(int extrinsicstate) { Console.WriteLine("具體實現類: intrinsicstate {0}, extrinsicstate {1}", intrinsicstate, extrinsicstate); } }
在享元模式的實現中,咱們沒有像以前同樣,把一個細粒度的類實例設計成一個單獨的類,而是把它做爲共享對象的內部狀態放在共享類的內部定義,具體的解釋註釋中都有了,你們能夠參考註釋去進一步理解享元模式。this
看完享元模式的實現以後,爲了幫助你們理清楚享元模式中各種之間的關係,下面給出上面實現代碼中的類圖,以下所示:spa
(摘自http://www.cnblogs.com/zhenyulu/articles/55793.html)設計
在上圖中,涉及的角色以下幾種角色:
抽象享元角色(Flyweight):此角色是全部的具體享元類的基類,爲這些類規定出須要實現的公共接口。那些須要外部狀態的操做能夠經過調用方法以參數形式傳入。
具體享元角色(ConcreteFlyweight):實現抽象享元角色所規定的接口。若是有內部狀態的話,能夠在類內部定義。
享元工廠角色(FlyweightFactory):本角色複雜建立和管理享元角色。本角色必須保證享元對象能夠被系統適當地共享,當一個客戶端對象調用一個享元對象的時候,享元工廠角色檢查系統中是否已經有一個符合要求的享元對象,若是已經存在,享元工廠角色就提供已存在的享元對象,若是系統中沒有一個符合的享元對象的話,享元工廠角色就應當建立一個合適的享元對象。
客戶端角色(Client):本角色須要存儲全部享元對象的外部狀態。
注:上面的實現只是單純的享元模式,同時還有複合的享元模式,因爲複合享元模式較複雜,這裏就不給出實現了。
分析完享元模式的實現以後,讓咱們繼續分析下享元模式的優缺點:
優勢:
下降了系統中對象的數量,從而下降了系統中細粒度對象給內存帶來的壓力。
缺點:
爲了使對象能夠共享,須要將一些狀態外部化,這使得程序的邏輯更復雜,使系統複雜化。
享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。
在下面全部條件都知足時,能夠考慮使用享元模式:
一個系統中有大量的對象;
這些對象耗費大量的內存;
這些對象中的狀態大部分均可以被外部化
這些對象能夠按照內部狀態分紅不少的組,當把外部對象從對象中剔除時,每個組均可以僅用一個對象代替
軟件系統不依賴這些對象的身份,
知足上面的條件的系統可使用享元模式。可是使用享元模式須要額外維護一個記錄子系統已有的全部享元的表,而這也須要耗費資源,因此,應當在有足夠多的享元實例可共享時才值得使用享元模式。
注:在.NET類庫中,string類的實現就使用了享元模式,更多內容能夠參考字符串駐留池的介紹,同時也能夠參考這個博文深刻理解.NET中string類的設計——http://www.cnblogs.com/artech/archive/2010/11/25/internedstring.html
到這裏,享元模式的介紹就結束了,享元模式主要用來解決因爲大量的細粒度對象所形成的內存開銷的問題,它在實際的開發中並不經常使用,能夠做爲底層的提高性能的一種手段。