C#設計模式之十一享元模式(Flyweight Pattern)【結構型】
一、引言
今天我們要講【結構型】設計模式的第六個模式,該模式是【享元模式】,英文名稱是:Flyweight Pattern。還是老套路,先從名字上來看看。“享元”是不是可以這樣理解,共享“單元”,單元是什麽呢,舉例說明,對於圖形而言就是圖元,對於英文來說就只26個英文字母,對於漢語來說就是每個漢字,也可以這樣理解“元”,構成事物的最小單元,這些單元如果大量、且重復出現,可以緩存重復出現的單元,達到節省內存的目的,換句說法就是享元是為了節省空間,對於計算機而言就是內存。面向對象很好地解決了系統抽象性的問題(系統抽象性指把系統裏面的事物寫成類,類可以實例化成為對象,用對象和對象之間的關系來設計系統),在大多數情況下,這樣做是不會損及系統的性能的。但是,在某些特殊的應用中,由於對象的數量太大,並且這些大量的對象中有很多是重復的,如果每個對象都單獨的創建(C#的語法是new)出來,會給系統帶來難以承受的內存開銷。比如圖形應用中的圖元等對象、字處理應用中的字符對象等。
二、享元模式的詳細介紹
2.1、動機(Motivate)
在軟件系統中,采用純粹對象方案的問題在於大量細粒度的對象會很快充斥在系統中,從而帶來很高的運行時代價——主要指內存需求方面的代價。如何在避免大量細粒度對象問題的同時,讓外部客戶程序仍然能夠透明地使用面向對象的方式來進行操作?
2.2、意圖(Intent)
運用共享技術有效地支持大量細粒度的對象。 ——《設計模式》GoF
2.3、結構圖(Structure)
i
2.4、模式的組成
(1)、抽象享元角色(Flyweight):此角色是所有的具體享元類的基類,為這些類規定出需要實現的公共接口。那些需要外部狀態的操作可以通過調用方法以參數形式傳入。
(2)、具體享元角色(ConcreteFlyweight)
(3)、享元工廠角色(FlyweightFactory):本角色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享,當一個客戶端對象調用一個享元對象的時候,享元工廠角色檢查系統中是否已經有一個符合要求的享元對象,如果已經存在,享元工廠角色就提供已存在的享元對象,如果系統中沒有一個符合的享元對象的話,享元工廠角色就應當創建一個合適的享元對象。
(4)、客戶端角色(Client):本角色需要存儲所有享元對象的外部狀態。
2.5、享元模式的具體代碼實現
說起“享元模式”,我這裏有一個很好的場景可以進行說明。我們知道在戰鬥的遊戲場景中,會有很多戰士,基本上戰士都是差不多的,小區別戰士忽略,最大的區別就是拿的武器不同而已。在大型的戰爭遊戲中,會有大量的士兵出來戰鬥,我們寫程序的時候就可以用“享元”來解決大量戰士的情況。
1 namespace 享元模式的實現 2 { 3 /// <summary> 4 /// 享元模式不是很難,但是有些狀態需要單獨處理,以下就是該模式的C#實現,有些輔助類,大家應該看得出吧,別混了。 5 /// </summary> 6 class Client 7 { 8 static void Main(string[] args) 9 { 10 //比如,我們現在需要10000個一般士兵,只需這樣 11 SoldierFactory factory = new SoldierFactory(); 12 AK47 ak47 = new AK47(); 13 for (int i = 0; i < 100; i++) 14 { 15 Soldier soldier = null; 16 if (i <= 20) 17 { 18 soldier = factory.GetSoldier("士兵" + (i + 1), ak47, SoldierType.Normal); 19 } 20 else 21 { 22 soldier = factory.GetSoldier("士兵" + (i + 1), ak47, SoldierType.Water); 23 } 24 soldier.Fight(); 25 } 26 //我們有這麽多的士兵,但是使用的內存不是很多,因為我們緩存了。 27 Console.Read(); 28 } 29 } 30 31 //這些是輔助類型 32 public enum SoldierType 33 { 34 Normal, 35 Water 36 } 37 38 //該類型就是抽象戰士Soldier--該類型相當於抽象享元角色 39 public abstract class Soldier 40 { 41 //通過構造函數初始化士兵的名稱 42 protected Soldier(string name) 43 { 44 this.Name = name; 45 } 46 47 //士兵的名字 48 public string Name { get; private set; } 49 50 //可以傳入不同的武器就用不同的活力---該方法相當於抽象Flyweight的Operation方法 51 public abstract void Fight(); 52 53 public Weapen WeapenInstance { get; set; } 54 } 55 56 //一般類型的戰士,武器就是步槍---相當於具體的Flyweight角色 57 public sealed class NormalSoldier : Soldier 58 { 59 //通過構造函數初始化士兵的名稱 60 public NormalSoldier(string name) : base(name) { } 61 62 //執行享元的方法---就是Flyweight類型的Operation方法 63 public override void Fight() 64 { 65 WeapenInstance.Fire("士兵:"+Name+" 在陸地執行擊斃任務"); 66 } 67 } 68 69 //這是海軍陸戰隊隊員,武器精良----相當於具體的Flyweight角色 70 public sealed class WaterSoldier : Soldier 71 { 72 //通過構造函數初始化士兵的名稱 73 public WaterSoldier(string name) : base(name) { } 74 75 //執行享元的方法---就是Flyweight類型的Operation方法 76 public override void Fight() 77 { 78 WeapenInstance.Fire("士兵:"+Name+" 在海中執行擊斃任務"); 79 } 80 } 81 82 //此類型和享元沒太大關系,可以算是享元對象的狀態吧,需要從外部定義 83 public abstract class Weapen 84 { 85 public abstract void Fire(string jobName); 86 } 87 88 //此類型和享元沒太大關系,可以算是享元對象的狀態吧,需要從外部定義 89 public sealed class AK47:Weapen 90 { 91 public override void Fire(string jobName) 92 { 93 Console.WriteLine(jobName); 94 } 95 } 96 97 //該類型相當於是享元的工廠---相當於FlyweightFactory類型 98 public sealed class SoldierFactory 99 { 100 private static IList<Soldier> soldiers; 101 102 static SoldierFactory() 103 { 104 soldiers = new List<Soldier>(); 105 } 106 107 Soldier mySoldier = null; 108 //因為我這裏有兩種士兵,所以在這裏可以增加另外一個參數,士兵類型,原模式裏面沒有, 109 public Soldier GetSoldier(string name, Weapen weapen, SoldierType soldierType) 110 { 111 foreach (Soldier soldier in soldiers) 112 { 113 if (string.Compare(soldier.Name, name, true) == 0) 114 { 115 mySoldier = soldier; 116 return mySoldier; 117 } 118 } 119 //我們這裏就任務名稱是唯一的 120 if (soldierType == SoldierType.Normal) 121 { 122 mySoldier = new NormalSoldier(name); 123 } 124 else 125 { 126 mySoldier = new WaterSoldier(name); 127 } 128 mySoldier.WeapenInstance = weapen; 129 130 soldiers.Add(mySoldier); 131 return mySoldier; 132 } 133 } 134 }
這個模式很簡單,就話不多說了。
三、享元模式的實現要點:
面向對象很好地解決了抽象性的問題,但是作為一個運行在機器中的程序實體,我們需要考慮對象的代價問題。Flyweight設計模式主要解決面向對象的代價問題,一般不觸及面向對象的抽象性問題。
Flyweight采用對象共享的做法來降低系統中對象的個數,從而降低細粒度對象給系統帶來的內存壓力。在具體實現方面,要註意對象狀態的處理。
對象的數量太大從而導致對象內存開銷加大——什麽樣的數量才算大?這需要我們仔細的根據具體應用情況進行評估,而不能憑空臆斷。
3.1】、享元模式的優點
(1)、享元模式的優點在於它能夠極大的減少系統中對象的個數。
(2)、享元模式由於使用了外部狀態,外部狀態相對獨立,不會影響到內部狀態,所以享元模式使得享元對象能夠在不同的環境被共享。
3.2】、享元模式的缺點
(1)、由於享元模式需要區分外部狀態和內部狀態,使得應用程序在某種程度上來說更加復雜化了。
(2)、為了使對象可以共享,享元模式需要將享元對象的狀態外部化,而讀取外部狀態使得運行時間變
3.3】、在下面所有條件都滿足時,可以考慮使用享元模式:
(1)、一個系統中有大量的對象;
(2)、這些對象耗費大量的內存;
(3)、這些對象中的狀態大部分都可以被外部化
(4)、這些對象可以按照內部狀態分成很多的組,當把外部對象從對象中剔除時,每一個組都可以僅用一個對象代替軟件系統不依賴這些對象的身份,
滿足上面的條件的系統可以使用享元模式。但是使用享元模式需要額外維護一個記錄子系統已有的所有享元的表,而這也需要耗費資源,所以,應當在有足夠多的享元實例可共享時才值得使用享元模式。
四、.NET 中享元模式的實現
.NET在C#中有一個Code Behind機制,它表面有一個aspx文件,背後又有一個cs文件,它的編譯過程實際上會把aspx文件解析成C#文件,然後編譯成dll,在這個過程中,我們在aspx中寫的任何html代碼都會轉化為literal control,literal control是一個一般的文本控件,它就表示html標記。當這些標記有一樣的時候,構建控件樹的時候就會用到Flyweight模式.
它的應用並不是那麽平凡,只有在效率空間確實不高的時候我們才用它。
五、總結
剛開始接觸這個模式的時候,感覺這個模式不是特別難,在我們編碼的過程中也有涉及,但是在學習的過程中也走了不少彎路,任何設計模式都有他特定的使用場景,小心誤用。這個模式在業務系統中相對而言使用的並不多,在類似遊戲場景中、字符處理等系統用的比較多。還是老話,通過叠代來使用模式,別為了模式而模式。今天就到這裏,以後繼續。
C#設計模式之十一享元模式(Flyweight Pattern)【結構型】