26享元模式(Flyweight Pattern)
面向對象的代價
??? 面向對象很好地解決了系統抽象性的問題,同時在大多數情況下,也不會損及系統的性能。但是,在
某些特殊的應用中下,由于對象的數量太大,采用面向對象會給系統帶來難以承受的內存開銷。比如:
圖形應用中的圖元等對象、字處理應用中的字符對象等。?
????
? ? ? ? ? ? ? ? ?? ? ? ??
?動機(Motivate):
??? 采用純粹對象方案的問題在于大量細粒度的對象會很快充斥在系統中,從而帶來很高的運行時代價--------主要指內存需求方面的代價。
??? 如何在避免大量細粒度對象問題的同時,讓外部客戶程序仍然能夠透明地使用面向對象的方式來進行操作?
意圖(Intent):
????運用共享技術有效地支持大量細粒度的對象。? -------《設計模式》GOF
?結構(Struct):
??????????????????
適用性:???
當以下所有的條件都滿足時,可以考慮使用享元模式:
1、???一個系統有大量的對象。
2、???這些對象耗費大量的內存。
3、???這些對象的狀態中的大部分都可以外部化。
4、???這些對象可以按照內蘊狀態分成很多的組,當把外蘊對象從對象中剔除時,每一個組都可以僅用一個對象代替。
5、???軟件系統不依賴于這些對象的身份,換言之,這些對象可以是不可分辨的。
滿足以上的這些條件的系統可以使用享元對象。最后,使用享元模式需要維護一個記錄了系統已有的所有享元的表,而這需要耗費資源。因此,應當在有足夠多的享元實例可供共享時才值得使用享元模式。
生活中的例子:
????享元模式使用共享技術有效地支持大量細粒度的對象。公共交換電話網(PSTN)是享元的一個例子。有一些資源例如撥號音發生器、振鈴發生器和撥號接收器是必須由所有用戶共享的。當一個用戶拿起聽筒打電話時,他不需要知道使用了多少資源。對于用戶而言所有的事情就是有撥號音,撥打號碼,撥通電話。?
????????????????????
代碼實現:
????Flyweight在拳擊比賽中指最輕量級,即“蠅量級”,這里翻譯為“享元”,可以理解為共享元對象(細粒度對象)的意思。提到Flyweight模式都會一般都會用編輯器例子來說明,這里也不例外,但我會嘗試著通過重構來看待Flyweight模式。考慮這樣一個字處理軟件,它需要處理的對象可能有單個的字符,由字符組成的段落以及整篇文檔,根據面向對象的設計思想和Composite模式,不管是字符還是段落,文檔都應該作為單個的對象去看待,這里只考慮單個的字符,不考慮段落及文檔等對象,于是可以很容易的得到下面的結構圖:
??????????????????
?
?1?//?"Charactor"
?2?public?abstract?class?Charactor
?3?{
?4?????//Fields
?5?????protected?char?_symbol;
?6?
?7?????protected?int?_width;
?8?
?9?????protected?int?_height;
10?
11?????protected?int?_ascent;
12?
13?????protected?int?_descent;
14?
15?????protected?int?_pointSize;
16?
17?????//Method
18?????public?abstract?void?Display();
19?}
20?
21?//?"CharactorA"
22?public?class?CharactorA?:?Charactor
23?{?
24?????//?Constructor?
25?????public?CharactorA()
26?????{
27???????this._symbol?=?'A';
28???????this._height?=?100;
29???????this._width?=?120;
30???????this._ascent?=?70;
31???????this._descent?=?0;
32???????this._pointSize?=?12;
33?????}
34?
35?????//Method
36?????public?override?void?Display()
37?????{
38?????????Console.WriteLine(this._symbol);
39?????}
40?}
41?
42?//?"CharactorB"
43?public?class?CharactorB?:?Charactor
44?{
45?????//?Constructor?
46?????public?CharactorB()
47?????{
48?????????this._symbol?=?'B';
49?????????this._height?=?100;
50?????????this._width?=?140;
51?????????this._ascent?=?72;
52?????????this._descent?=?0;
53?????????this._pointSize?=?10;
54?????}
55?
56?????//Method
57?????public?override?void?Display()
58?????{
59?????????Console.WriteLine(this._symbol);
60?????}
61?}
62?
63?//?"CharactorC"
64?public?class?CharactorC?:?Charactor
65?{
66?????//?Constructor?
67?????public?CharactorC()
68?????{
69?????????this._symbol?=?'C';
70?????????this._height?=?100;
71?????????this._width?=?160;
72?????????this._ascent?=?74;
73?????????this._descent?=?0;
74?????????this._pointSize?=?14;
75?????}
76?
77?????//Method
78?????public?override?void?Display()
79?????{
80?????????Console.WriteLine(this._symbol);
81?????}
82?}
????好了,現在看到的這段代碼可以說是很好地符合了面向對象的思想,但是同時我們也為此付出了沉重的代價,那就是性能上的開銷,可以想象,在一篇文檔中,字符的數量遠不止幾百個這么簡單,可能上千上萬,內存中就同時存在了上千上萬個Charactor對象,這樣的內存開銷是可想而知的。進一步分析可以發現,雖然我們需要的Charactor實例非常多,這些實例之間只不過是狀態不同而已,也就是說這些實例的狀態數量是很少的。所以我們并不需要這么多的獨立的Charactor實例,而只需要為每一種Charactor狀態創建一個實例,讓整個字符處理軟件共享這些實例就可以了。看這樣一幅示意圖:
????? ???????????????
??? 現在我們看到的A,B,C三個字符是共享的,也就是說如果文檔中任何地方需要這三個字符,只需要使用共享的這三個實例就可以了。然而我們發現單純的這樣共享也是有問題的。雖然文檔中的用到了很多的A字符,雖然字符的symbol等是相同的,它可以共享;但是它們的pointSize卻是不相同的,即字符在文檔中中的大小是不相同的,這個狀態不可以共享。為解決這個問題,首先我們將不可共享的狀態從類里面剔除出去,即去掉pointSize這個狀態(只是暫時的J),類結構圖如下所示:
????????
?1?//?"Charactor"
?2?public?abstract?class?Charactor
?3?{
?4?????//Fields
?5?????protected?char?_symbol;
?6?
?7?????protected?int?_width;
?8?
?9?????protected?int?_height;
10?
11?????protected?int?_ascent;
12?
13?????protected?int?_descent;
14?
15?????//Method
16?????public?abstract?void?Display();
17?}
18?
19?//?"CharactorA"
20?public?class?CharactorA?:?Charactor
21?{
22?????//?Constructor?
23?????public?CharactorA()
24?????{
25?????????this._symbol?=?'A';
26?????????this._height?=?100;
27?????????this._width?=?120;
28?????????this._ascent?=?70;
29?????????this._descent?=?0;
30?????}
31?
32?????//Method
33?????public?override?void?Display()
34?????{
35?????????Console.WriteLine(this._symbol);
36?????}
37?}
38?
39?//?"CharactorB"
40?public?class?CharactorB?:?Charactor
41?{
42?????//?Constructor?
43?????public?CharactorB()
44?????{
45?????????this._symbol?=?'B';
46?????????this._height?=?100;
47?????????this._width?=?140;
48?????????this._ascent?=?72;
49?????????this._descent?=?0;
50?????}
51?
52?????//Method
53?????public?override?void?Display()
54?????{
55?????????Console.WriteLine(this._symbol);
56?????}
57?}
58?
59?//?"CharactorC"
60?public?class?CharactorC?:?Charactor
61?{
62?????//?Constructor?
63?????public?CharactorC()
64?????{
65?????????this._symbol?=?'C';
66?????????this._height?=?100;
67?????????this._width?=?160;
68?????????this._ascent?=?74;
69?????????this._descent?=?0;
70?????}
71?
72?????//Method
73?????public?override?void?Display()
74?????{
75?????????Console.WriteLine(this._symbol);
76?????}
77?}
好,現在類里面剩下的狀態都可以共享了,下面我們要做的工作就是控制Charactor類的創建過程,即如果已經存在了“A”字符這樣的實例,就不需要再創建,直接返回實例;如果沒有,則創建一個新的實例。如果把這項工作交給Charactor類,即Charactor類在負責它自身職責的同時也要負責管理Charactor實例的管理工作,這在一定程度上有可能違背類的單一職責原則,因此,需要一個單獨的類來做這項工作,引入CharactorFactory類,結構圖如下:
??????????
?1?//?"CharactorFactory"
?2?public?class?CharactorFactory
?3?{
?4?????//?Fields
?5?????private?Hashtable?charactors?=?new?Hashtable();
?6?
?7?????//?Constructor?
?8?????public?CharactorFactory()
?9?????{
10?????????charactors.Add("A",?new?CharactorA());
11?????????charactors.Add("B",?new?CharactorB());
12?????????charactors.Add("C",?new?CharactorC());
13?????}
14????????
15?????//?Method
16?????public?Charactor?GetCharactor(string?key)
17?????{
18?????????Charactor?charactor?=?charactors[key]?as?Charactor;
19?
20?????????if?(charactor?==?null)
21?????????{
22?????????????switch?(key)
23?????????????{
24?????????????????case?"A":?charactor?=?new?CharactorA();?break;
25?????????????????case?"B":?charactor?=?new?CharactorB();?break;?
26?????????????????case?"C":?charactor?=?new?CharactorC();?break;
27?????????????????//
28?????????????}
29?????????????charactors.Add(key,?charactor);
30?????????}
31?????????return?charactor;
32?????}
33?}
到這里已經完全解決了可以共享的狀態(這里很丑陋的一個地方是出現了switch語句,但這可以通過別的辦法消除,為了簡單期間我們先保持這種寫法)。下面的工作就是處理剛才被我們剔除出去的那些不可共享的狀態,因為雖然將那些狀態移除了,但是Charactor對象仍然需要這些狀態,被我們剝離后這些對象根本就無法工作,所以需要將這些狀態外部化。首先會想到一種比較簡單的解決方案就是對于不能共享的那些狀態,不需要去在Charactor類中設置,而直接在客戶程序代碼中進行設置,類結構圖如下:
??? ??????????
?1?public?class?Program
?2?{
?3?????public?static?void?Main()
?4?????{
?5?????????Charactor?ca?=?new?CharactorA();
?6?????????Charactor?cb?=?new?CharactorB();
?7?????????Charactor?cc?=?new?CharactorC();
?8?
?9?????????//顯示字符
10?
11?????????//設置字符的大小ChangeSize();
12?????}
13?
14?????public?void?ChangeSize()
15?????{
16?????????//在這里設置字符的大小
17?????}
18?}
按照這樣的實現思路,可以發現如果有多個客戶端程序使用的話,會出現大量的重復性的邏輯,用重構的術語來說是出現了代碼的壞味道,不利于代碼的復用和維護;另外把這些狀態和行為移到客戶程序里面破壞了封裝性的原則。再次轉變我們的實現思路,可以確定的是這些狀態仍然屬于Charactor對象,所以它還是應該出現在Charactor類中,對于不同的狀態可以采取在客戶程序中通過參數化的方式傳入。類結構圖如下:
??? ???
?
??1?//?"Charactor"
??2?public?abstract?class?Charactor
??3?{
??4?????//Fields
??5?????protected?char?_symbol;
??6?
??7?????protected?int?_width;
??8?
??9?????protected?int?_height;
?10?
?11?????protected?int?_ascent;
?12?
?13?????protected?int?_descent;
?14?
?15?????protected?int?_pointSize;
?16?
?17?????//Method
?18?????public?abstract?void?SetPointSize(int?size);
?19?????public?abstract?void?Display();
?20?}
?21?
?22?//?"CharactorA"
?23?public?class?CharactorA?:?Charactor
?24?{
?25?????//?Constructor?
?26?????public?CharactorA()
?27?????{
?28?????????this._symbol?=?'A';
?29?????????this._height?=?100;
?30?????????this._width?=?120;
?31?????????this._ascent?=?70;
?32?????????this._descent?=?0;
?33?????}
?34?
?35?????//Method
?36?????public?override?void?SetPointSize(int?size)
?37?????{
?38?????????this._pointSize?=?size;
?39?????}
?40?
?41?????public?override?void?Display()
?42?????{
?43?????????Console.WriteLine(this._symbol?+
?44???????????"pointsize:"?+?this._pointSize);
?45?????}
?46?}
?47?
?48?//?"CharactorB"
?49?public?class?CharactorB?:?Charactor
?50?{
?51?????//?Constructor?
?52?????public?CharactorB()
?53?????{
?54?????????this._symbol?=?'B';
?55?????????this._height?=?100;
?56?????????this._width?=?140;
?57?????????this._ascent?=?72;
?58?????????this._descent?=?0;
?59?????}
?60?
?61?????//Method
?62?????public?override?void?SetPointSize(int?size)
?63?????{
?64?????????this._pointSize?=?size;
?65?????}
?66?
?67?????public?override?void?Display()
?68?????{
?69?????????Console.WriteLine(this._symbol?+
?70???????????"pointsize:"?+?this._pointSize);
?71?????}
?72?}
?73?
?74?//?"CharactorC"
?75?public?class?CharactorC?:?Charactor
?76?{
?77?????//?Constructor?
?78?????public?CharactorC()
?79?????{
?80?????????this._symbol?=?'C';
?81?????????this._height?=?100;
?82?????????this._width?=?160;
?83?????????this._ascent?=?74;
?84?????????this._descent?=?0;
?85?????}
?86?
?87?????//Method
?88?????public?override?void?SetPointSize(int?size)
?89?????{
?90?????????this._pointSize?=?size;
?91?????}
?92?
?93?????public?override?void?Display()
?94?????{
?95?????????Console.WriteLine(this._symbol?+
?96???????????"pointsize:"?+?this._pointSize);
?97?????}
?98?}
?99?
100?//?"CharactorFactory"
101?public?class?CharactorFactory
102?{
103?????//?Fields
104?????private?Hashtable?charactors?=?new?Hashtable();
105?
106?????//?Constructor?
107?????public?CharactorFactory()
108?????{
109?????????charactors.Add("A",?new?CharactorA());
110?????????charactors.Add("B",?new?CharactorB());
111?????????charactors.Add("C",?new?CharactorC());
112?????}
113????????
114?????//?Method
115?????public?Charactor?GetCharactor(string?key)
116?????{
117?????????Charactor?charactor?=?charactors[key]?as?Charactor;
118?
119?????????if?(charactor?==?null)
120?????????{
121?????????????switch?(key)
122?????????????{
123?????????????????case?"A":?charactor?=?new?CharactorA();?break;
124?????????????????case?"B":?charactor?=?new?CharactorB();?break;?
125?????????????????case?"C":?charactor?=?new?CharactorC();?break;
126?????????????????//
127?????????????}
128?????????????charactors.Add(key,?charactor);
129?????????}
130?????????return?charactor;
131?????}
132?}
133?
134?public?class?Program
135?{
136?????public?static?void?Main()
137?????{
138?????????CharactorFactory?factory?=?new?CharactorFactory();
139?
140?????????//?Charactor?"A"
141?????????CharactorA?ca?=?(CharactorA)factory.GetCharactor("A");
142?????????ca.SetPointSize(12);
143?????????ca.Display();
144?????????
145?????????//?Charactor?"B"
146?????????CharactorB?cb?=?(CharactorB)factory.GetCharactor("B");
147?????????ca.SetPointSize(10);
148?????????ca.Display();
149?
150?????????//?Charactor?"C"
151?????????CharactorC?cc?=?(CharactorC)factory.GetCharactor("C");
152?????????ca.SetPointSize(14);
153?????????ca.Display();
154?????}
155?}
可以看到這樣的實現明顯優于第一種實現思路。好了,到這里我們就到到了通過Flyweight模式實現了優化資源的這樣一個目的。在這個過程中,還有如下幾點需要說明:
1.引入CharactorFactory是個關鍵,在這里創建對象已經不是new一個Charactor對象那么簡單,而必須用工廠方法封裝起來。
2.在這個例子中把Charactor對象作為Flyweight對象是否準確值的考慮,這里只是為了說明Flyweight模式,至于在實際應用中,哪些對象需要作為Flyweight對象是要經過很好的計算得知,而絕不是憑空臆想。
3.區分內外部狀態很重要,這是享元對象能做到享元的關鍵所在。
到這里,其實我們的討論還沒有結束。有人可能會提出如下問題,享元對象(Charactor) 在這個系統中相對于每一個內部狀態而言它是唯一的,這跟單件模式有什么區別呢?這個問題已經很好回答了,那就是單件類是不能直接被實例化的,而享元類是可 以被實例化的。事實上在這里面真正被設計為單件的應該是享元工廠(不是享元)類,因為如果創建很多個享元工廠的實例,那我們所做的一切努力都是白費的,并 沒有減少對象的個數。修改后的類結構圖如下:
??? ??????
?1?//?"CharactorFactory"
?2?public?class?CharactorFactory
?3?{
?4?????//?Fields
?5?????private?Hashtable?charactors?=?new?Hashtable();
?6?
?7?????private?CharactorFactory?instance;
?8?????//?Constructor?
?9?????private?CharactorFactory()
10?????{
11?????????charactors.Add("A",?new?CharactorA());
12?????????charactors.Add("B",?new?CharactorB());
13?????????charactors.Add("C",?new?CharactorC());
14?????}
15?????
16?????//?Property
17?????public?CharactorFactory?Instance
18?????{
19?????????get?
20?????????{
21?????????????if?(instance?!=?null)
22?????????????{
23?????????????????instance?=?new?CharactorFactory();
24?????????????}
25?????????????return?instance;
26?????????}
27?????}
28?
29?????//?Method
30?????public?Charactor?GetCharactor(string?key)
31?????{
32?????????Charactor?charactor?=?charactors[key]?as?Charactor;
33?
34?????????if?(charactor?==?null)
35?????????{
36?????????????switch?(key)
37?????????????{
38?????????????????case?"A":?charactor?=?new?CharactorA();?break;
39?????????????????case?"B":?charactor?=?new?CharactorB();?break;?
40?????????????????case?"C":?charactor?=?new?CharactorC();?break;
41?????????????????//
42?????????????}
43?????????????charactors.Add(key,?charactor);
44?????????}
45?????????return?charactor;
46?????}
47?}
.NET框架中的應用:
??? Flyweight更多時候的時候一種底層的設計模式,在我們的實際應用程序中使用的并不是很多。在.NET中的String類型其實就是運用了Flyweight模式。可以想象,如果每次執行string s1 = “abcd”操作,都創建一個新的字符串對象的話,內存的開銷會很大。所以.NET中如果第一次創建了這樣的一個字符串對象s1,下次再創建相同的字符串s2時只是把它的引用指向“abcd”,這樣就實現了“abcd”在內存中的共享。可以通過下面一個簡單的程序來演示s1和s2的引用是否一致:
?1?public?class?Program
?2?{
?3?????public?static?void?Main(string[]?args)
?4?????{
?5?????????string?s1?=?"abcd";
?6?????????string?s2?=?"abcd";
?7?
?8?????????Console.WriteLine(Object.ReferenceEquals(s1,s2));
?9?
10?????????Console.ReadLine();
11?????}
12?}
Flyweight實現要點:
1.面向對象很好的解決了抽象性的問題,但是作為一個運行在機器中的程序實體,我們需要考慮對象的代價問題。Flyweight設計模式主要解決面向對象的代價問題,一般不觸及面向對象的抽象性問題。
2.Flyweight采用對象共享的做法來降低系統中對象的個數,從而降低細粒度對象給系統帶來的內存壓力。在具體實現方面,要注意對象狀態的處理。
3.享元模式的優點在于它大幅度地降低內存中對象的數量。但是,它做到這一點所付出的代價也是很高的:享元模式使得系統更加復雜。為了使對象可以共享,需要將一些狀態外部化,這使得程序的邏輯復雜化。另外它將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。
總結
以上是生活随笔為你收集整理的26享元模式(Flyweight Pattern)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 财政赤字将增万亿,我国赤字率安排公布后,
- 下一篇: 个人储户更喜欢把钱存在大型银行,而且更喜