天天看點

C#設計模式(12)——享元模式(Flyweight Pattern)

在軟體開發過程,如果我們需要重複使用某個對象的時候,如果我們重複地使用new建立這個對象的話,這樣我們在記憶體就需要多次地去申請記憶體空間了,這樣可能會出現記憶體使用越來越多的情況,這樣的問題是非常嚴重,然而享元模式可以解決這個問題,下面具體看看享元模式是如何去解決這個問題的。

在前面說了,享元模式可以解決上面的問題了,在介紹享元模式之前,讓我們先要分析下如果去解決上面那個問題,上面的問題就是重複建立了同一個對象,如果讓我們去解決這個問題肯定會這樣想:“既然都是同一個對象,能不能隻建立一個對象,然後下次需要建立這個對象的時候,讓它直接用已經建立好了的對象就好了”,也就是說——讓一個對象共享。不錯,這個也是享元模式的實作精髓所在。

介紹完享元模式的精髓之後,讓我們具體看看享元模式的正式定義:

享元模式——運用共享技術有效地支援大量細粒度的對象。享元模式可以避免大量相似類的開銷,在軟體開發中如果需要生成大量細粒度的類執行個體來表示資料,如果這些執行個體除了幾個參數外基本上都是相同的,這時候就可以使用享元模式來大幅度減少需要執行個體化類的數量。如果能把這些參數(指的這些類執行個體不同的參數)移動類執行個體外面,在方法調用時将他們傳遞進來,這樣就可以通過共享大幅度地減少單個執行個體的數目。(這個也是享元模式的實作要領),然而我們把類執行個體外面的參數稱為享元對象的外部狀态,把在享元對象内部定義稱為内部狀态。具體享元對象的内部狀态與外部狀态的定義為:

内部狀态:在享元對象的内部并且不會随着環境的改變而改變的共享部分

外部狀态:随環境改變而改變的,不可以共享的狀态。

分析完享元模式的實作思路之後,相信大家實作享元模式肯定沒什麼問題了,下面以一個世紀的應用來實作下享元模式。這個例子是:一個文本編輯器中會出現很多字面,使用享元模式去實作這個文本編輯器的話,會把每個字面做成一個享元對象。享元對象的内部狀态就是這個字面,而字母在文本中的位置和字型風格等其他資訊就是它的外部狀态。下面就以這個例子來實作下享元模式,具體實作代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

<code>/// &lt;summary&gt;</code>

<code>    </code><code>/// 用戶端調用</code>

<code>    </code><code>/// &lt;/summary&gt;</code>

<code>    </code><code>class</code> <code>Client</code>

<code>    </code><code>{</code>

<code>        </code><code>static</code> <code>void</code> <code>Main(</code><code>string</code><code>[] args)</code>

<code>        </code><code>{</code>

<code>            </code><code>// 定義外部狀态,例如字母的位置等資訊</code>

<code>            </code><code>int</code> <code>externalstate = 10;</code>

<code>            </code><code>// 初始化享元工廠</code>

<code>            </code><code>FlyweightFactory factory = </code><code>new</code> <code>FlyweightFactory();</code>

<code>            </code><code>// 判斷是否已經建立了字母A,如果已經建立就直接使用建立的對象A</code>

<code>            </code><code>Flyweight fa = factory.GetFlyweight(</code><code>"A"</code><code>);</code>

<code>            </code><code>if</code> <code>(fa != </code><code>null</code><code>)</code>

<code>            </code><code>{</code>

<code>                </code><code>// 把外部狀态作為享元對象的方法調用參數</code>

<code>                </code><code>fa.Operation(--externalstate);</code>

<code>            </code><code>}</code>

<code>            </code><code>// 判斷是否已經建立了字母B</code>

<code>            </code><code>Flyweight fb = factory.GetFlyweight(</code><code>"B"</code><code>);</code>

<code>            </code><code>if</code> <code>(fb != </code><code>null</code><code>)</code>

<code>                </code><code>fb.Operation(--externalstate);</code>

<code>            </code><code>// 判斷是否已經建立了字母C</code>

<code>            </code><code>Flyweight fc = factory.GetFlyweight(</code><code>"C"</code><code>);</code>

<code>            </code><code>if</code> <code>(fc != </code><code>null</code><code>)</code>

<code>                </code><code>fc.Operation(--externalstate);</code>

<code>            </code><code>// 判斷是否已經建立了字母D</code>

<code>            </code><code>Flyweight fd= factory.GetFlyweight(</code><code>"D"</code><code>);</code>

<code>            </code><code>if</code> <code>(fd != </code><code>null</code><code>)</code>

<code>                </code><code>fd.Operation(--externalstate);</code>

<code>            </code><code>else</code>

<code>                </code><code>Console.WriteLine(</code><code>"駐留池中不存在字元串D"</code><code>);</code>

<code>                </code><code>// 這時候就需要建立一個對象并放入駐留池中</code>

<code>                </code><code>ConcreteFlyweight d = </code><code>new</code> <code>ConcreteFlyweight(</code><code>"D"</code><code>);</code>

<code>                </code><code>factory.flyweights.Add(</code><code>"D"</code><code>, d);</code>

<code>            </code><code>Console.Read();</code>

<code>        </code><code>}</code>

<code>    </code><code>}</code>

<code>    </code><code>/// &lt;summary&gt;</code>

<code>    </code><code>/// 享元工廠,負責建立和管理享元對象</code>

<code>    </code><code>public</code> <code>class</code> <code>FlyweightFactory</code>

<code>        </code><code>// 最好使用泛型Dictionary&lt;string,Flyweighy&gt;</code>

<code>        </code><code>//public Dictionary&lt;string, Flyweight&gt; flyweights = new Dictionary&lt;string, Flyweight&gt;();</code>

<code>        </code><code>public</code> <code>Hashtable flyweights = </code><code>new</code> <code>Hashtable();</code>

<code>        </code><code>public</code> <code>FlyweightFactory()</code>

<code>            </code><code>flyweights.Add(</code><code>"A"</code><code>, </code><code>new</code> <code>ConcreteFlyweight(</code><code>"A"</code><code>));</code>

<code>            </code><code>flyweights.Add(</code><code>"B"</code><code>, </code><code>new</code> <code>ConcreteFlyweight(</code><code>"B"</code><code>));</code>

<code>            </code><code>flyweights.Add(</code><code>"C"</code><code>, </code><code>new</code> <code>ConcreteFlyweight(</code><code>"C"</code><code>));</code>

<code>        </code><code>public</code> <code>Flyweight GetFlyweight(</code><code>string</code> <code>key)</code>

<code>// 更好的實作如下</code>

<code>            </code><code>//Flyweight flyweight = flyweights[key] as Flyweight;</code>

<code>            </code><code>//if (flyweight == null)</code>

<code>            </code><code>//{</code>

<code>            </code><code>//    Console.WriteLine("駐留池中不存在字元串" + key);</code>

<code>            </code><code>//    flyweight = new ConcreteFlyweight(key);</code>

<code>            </code><code>//}</code>

<code>            </code><code>//return flyweight;</code>

<code>return</code> <code>flyweights[key] </code><code>as</code> <code>Flyweight;</code>

<code>    </code><code>///  抽象享元類,提供具體享元類具有的方法</code>

<code>    </code><code>public</code> <code>abstract</code> <code>class</code> <code>Flyweight</code>

<code>        </code><code>public</code> <code>abstract</code> <code>void</code> <code>Operation(</code><code>int</code> <code>extrinsicstate);</code>

<code>    </code><code>// 具體的享元對象,這樣我們不把每個字母設計成一個單獨的類了,而是作為把共享的字母作為享元對象的内部狀态</code>

<code>    </code><code>public</code> <code>class</code> <code>ConcreteFlyweight : Flyweight</code>

<code>        </code><code>// 内部狀态</code>

<code>        </code><code>private</code> <code>string</code> <code>intrinsicstate ;</code>

<code>        </code><code>// 構造函數</code>

<code>        </code><code>public</code> <code>ConcreteFlyweight(</code><code>string</code> <code>innerState)</code>

<code>            </code><code>this</code><code>.intrinsicstate = innerState;</code>

<code>        </code><code>/// &lt;summary&gt;</code>

<code>        </code><code>/// 享元類的執行個體方法</code>

<code>        </code><code>/// &lt;/summary&gt;</code>

<code>        </code><code>/// &lt;param name="extrinsicstate"&gt;外部狀态&lt;/param&gt;</code>

<code>        </code><code>public</code> <code>override</code> <code>void</code> <code>Operation(</code><code>int</code> <code>extrinsicstate)</code>

<code>            </code><code>Console.WriteLine(</code><code>"具體實作類: intrinsicstate {0}, extrinsicstate {1}"</code><code>, intrinsicstate, extrinsicstate);</code>

在享元模式的實作中,我們沒有像之前一樣,把一個細粒度的類執行個體設計成一個單獨的類,而是把它作為共享對象的内部狀态放在共享類的内部定義,具體的解釋注釋中都有了,大家可以參考注釋去進一步了解享元模式。

看完享元模式的實作之後,為了幫助大家理清楚享元模式中各類之間的關系,下面給出上面實作代碼中的類圖,如下所示:

在上圖中,涉及的角色如下幾種角色:

抽象享元角色(Flyweight):此角色是所有的具體享元類的基類,為這些類規定出需要實作的公共接口。那些需要外部狀态的操作可以通過調用方法以參數形式傳入。

具體享元角色(ConcreteFlyweight):實作抽象享元角色所規定的接口。如果有内部狀态的話,可以在類内部定義。

享元工廠角色(FlyweightFactory):本角色複雜建立和管理享元角色。本角色必須保證享元對象可以被系統适當地共享,當一個用戶端對象調用一個享元對象的時候,享元工廠角色檢查系統中是否已經有一個符合要求的享元對象,如果已經存在,享元工廠角色就提供已存在的享元對象,如果系統中沒有一個符合的享元對象的話,享元工廠角色就應當建立一個合适的享元對象。

用戶端角色(Client):本角色需要存儲所有享元對象的外部狀态。

注:上面的實作隻是單純的享元模式,同時還有複合的享元模式,由于複合享元模式較複雜,這裡就不給出實作了。

分析完享元模式的實作之後,讓我們繼續分析下享元模式的優缺點:

優點:

降低了系統中對象的數量,進而降低了系統中細粒度對象給記憶體帶來的壓力。

缺點:

為了使對象可以共享,需要将一些狀态外部化,這使得程式的邏輯更複雜,使系統複雜化。

享元模式将享元對象的狀态外部化,而讀取外部狀态使得運作時間稍微變長。

在下面所有條件都滿足時,可以考慮使用享元模式:

一個系統中有大量的對象;

這些對象耗費大量的記憶體;

這些對象中的狀态大部分都可以被外部化

這些對象可以按照内部狀态分成很多的組,當把外部對象從對象中剔除時,每一個組都可以僅用一個對象代替

軟體系統不依賴這些對象的身份,

滿足上面的條件的系統可以使用享元模式。但是使用享元模式需要額外維護一個記錄子系統已有的所有享元的表,而這也需要耗費資源,是以,應當在有足夠多的享元執行個體可共享時才值得使用享元模式。

到這裡,享元模式的介紹就結束了,享元模式主要用來解決由于大量的細粒度對象所造成的記憶體開銷的問題,它在實際的開發中并不常用,可以作為底層的提升性能的一種手段。

<a href="http://down.51cto.com/data/2363665" target="_blank">附件:http://down.51cto.com/data/2363665</a>

     本文轉自LearningHard 51CTO部落格,原文連結:http://blog.51cto.com/learninghard/1315781,如需轉載請自行聯系原作者