亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

(第Ⅲ部分 結構型模式篇) 第12章 享元模式(Fly

系統 2631 0
——.NET設計模式系列之十三
Terrylee ,2006年3月
摘要: 面向對象的思想很好地解決了抽象性的問題,一般也不會出現性能上的問題。但是在某些情況下,對象的數量可能會太多,從而導致了運行時的代價。那么我們如何去避免大量細粒度的對象,同時又不影響客戶程序使用面向對象的方式進行操作?
本文試圖通過一個簡單的字符處理的例子,運用重構的手段,一步步帶你走進Flyweight模式,在這個過程中我們一同思考、探索、權衡,通過比較而得出好的實現方式,而不是給你最終的一個完美解決方案。
主要內容:
1. Flyweight 模式解說
2 ..NET中的Flyweight模式
3 .Flyweight模式的實現要點
……
概述
面向對象的思想很好地解決了抽象性的問題,一般也不會出現性能上的問題。但是在某些情況下,對象的數量可能會太多,從而導致了運行時的代價。那么我們如何去避免大量細粒度的對象,同時又不影響客戶程序使用面向對象的方式進行操作?
意圖
運用共享技術有效地支持大量細粒度的對象。 [GOF 《設計模式》 ]
結構圖
1Flyweight 模式結構圖
生活中的例子
享元模式使用共享技術有效地支持大量細粒度的對象。公共交換電話網( PSTN )是享元的一個例子。有一些資源例如撥號音發生器、振鈴發生器和撥號接收器是必須由所有用戶共享的。當一個用戶拿起聽筒打電話時,他不需要知道使用了多少資源。對于用戶而言所有的事情就是有撥號音,撥打號碼,撥通電話。
2 使用撥號音發生器例子的享元模式對象圖
Flyweight 模式解說
Flyweight 在拳擊比賽中指最輕量級,即“蠅量級”,這里翻譯為“享元”,可以理解為共享元對象(細粒度對象)的意思。提到Flyweight模式都會一般都會用編輯器例子來說明,這里也不例外,但我會嘗試著通過重構來看待Flyweight模式。考慮這樣一個字處理軟件,它需要處理的對象可能有單個的字符,由字符組成的段落以及整篇文檔,根據面向對象的設計思想和Composite模式,不管是字符還是段落,文檔都應該作為單個的對象去看待, 這里只考慮單個的字符,不考慮段落及文檔等對象, 于是可以很容易的得到下面的結構圖:
圖3
示意性實現代碼:
Charactor 對象,這樣的內存開銷是可想而知的。進一步分析可以發現,雖然我們需要的 Charactor 實例非常多,這些實例之間只不過是狀態不同而已,也就是說這些實例的狀態數量是很少的。所以我們并不需要這么多的獨立的 Charactor 實例,而只需要為每一種 Charactor 狀態創建一個實例,讓整個字符處理軟件共享這些實例就可以了。看這樣一幅示意圖:
// "Charactor"
public abstract class Charactor
{
// Fields
protected char _symbol;

protected int _width;

protected int _height;

protected int _ascent;

protected int _descent;

protected int _pointSize;

// Method
public abstract void Display();
}


// "CharactorA"
public class CharactorA:Charactor
{
// Constructor
public CharactorA()
{
this ._symbol = ' A ' ;
this ._height = 100 ;
this ._width = 120 ;
this ._ascent = 70 ;
this ._descent = 0 ;
this ._pointSize = 12 ;
}


// Method
public override void Display()
{
Console.WriteLine(
this ._symbol);
}

}


// "CharactorB"
public class CharactorB:Charactor
{
// Constructor
public CharactorB()
{
this ._symbol = ' B ' ;
this ._height = 100 ;
this ._width = 140 ;
this ._ascent = 72 ;
this ._descent = 0 ;
this ._pointSize = 10 ;
}


// Method
public override void Display()
{
Console.WriteLine(
this ._symbol);
}

}


// "CharactorC"
public class CharactorC:Charactor
{
// Constructor
public CharactorC()
{
this ._symbol = ' C ' ;
this ._height = 100 ;
this ._width = 160 ;
this ._ascent = 74 ;
this ._descent = 0 ;
this ._pointSize = 14 ;
}


// Method
public override void Display()
{
Console.WriteLine(
this ._symbol);
}

}

好了,現在看到的這段代碼可以說是很好地符合了面向對象的思想,但是同時我們也為此付出了沉重的代價,那就是性能上的開銷,可以想象,在一篇文檔中,字符的數量遠不止幾百個這么簡單,可能上千上萬,內存中就同時存在了上千上萬個
4
現在我們看到的 A B C 三個字符是共享的,也就是說如果文檔中任何地方需要這三個字符,只需要使用共享的這三個實例就可以了。然而我們發現單純的這樣共享也是有問題的。雖然文檔中的用到了很多的 A 字符,雖然字符的 symbol 是相同的,它可以共享;但是它們的 pointSize 卻是不相同的,即字符在文檔中中的大小是不相同的,這個狀態不可以共享。為解決這個問題,首先我們將不可共享的狀態從類里面剔除出去,即去掉 pointSize 個狀態(只是暫時的 J ),類結構圖如下所示:
5
示意性實現代碼:
Charactor 類的創建過程,即如果已經存在了“ A ”字符這樣的實例,就不需要再創建,直接返回實例;如果沒有,則創建一個新的實例。如果把這項工作交給 Charactor 類,即 Charactor 類在負責它自身職責的同時也要負責管理 Charactor 實例的管理工作,這在一定程度上有可能違背類的單一職責原則,因此,需要一個單獨的類來做這項工作,引入 CharactorFactory 類,結構圖如下:
// "Charactor"
public abstract class Charactor
{
// Fields
protected char _symbol;

protected int _width;

protected int _height;

protected int _ascent;

protected int _descent;

// Method
public abstract void Display();
}


// "CharactorA"
public class CharactorA:Charactor
{
// Constructor
public CharactorA()
{
this ._symbol = ' A ' ;
this ._height = 100 ;
this ._width = 120 ;
this ._ascent = 70 ;
this ._descent = 0 ;
}


// Method
public override void Display()
{
Console.WriteLine(
this ._symbol);
}

}


// "CharactorB"
public class CharactorB:Charactor
{
// Constructor
public CharactorB()
{
this ._symbol = ' B ' ;
this ._height = 100 ;
this ._width = 140 ;
this ._ascent = 72 ;
this ._descent = 0 ;
}


// Method
public override void Display()
{
Console.WriteLine(
this ._symbol);
}

}


// "CharactorC"
public class CharactorC:Charactor
{
// Constructor
public CharactorC()
{
this ._symbol = ' C ' ;
this ._height = 100 ;
this ._width = 160 ;
this ._ascent = 74 ;
this ._descent = 0 ;
}


// Method
public override void Display()
{
Console.WriteLine(
this ._symbol);
}

}

好,現在類里面剩下的狀態都可以共享了,下面我們要做的工作就是控制
6
示意性實現代碼: switch 語句,但這可以通過別的辦法消除,為了簡單期間我們先保持這種寫法)。下面的工作就是處理剛才被我們剔除出去的那些不可共享的狀態,因為雖然將那些狀態移除了,但是 Charactor 對象仍然需要這些狀態,被我們剝離后這些對象根本就無法工作,所以需要將這些狀態外部化。首先會想到一種比較簡單的解決方案就是對于不能共享的那些狀態,不需要去在 Charactor 類中設置,而直接在客戶程序代碼中進行設置,類結構圖如下:
// "CharactorFactory"
public class CharactorFactory
{
// Fields
private Hashtablecharactors = new Hashtable();

// Constructor
public CharactorFactory()
{
charactors.Add(
" A " , new CharactorA());
charactors.Add(
" B " , new CharactorB());
charactors.Add(
" C " , new CharactorC());
}


// Method
public CharactorGetCharactor( string key)
{
Charactorcharactor
= charactors[key] as Charactor;

if (charactor == null )
{
switch (key)
{
case " A " :charactor = new CharactorA(); break ;
case " B " :charactor = new CharactorB(); break ;
case " C " :charactor = new CharactorC(); break ;
//
}

charactors.Add(key,charactor);
}

return charactor;
}

}

到這里已經完全解決了可以共享的狀態(這里很丑陋的一個地方是出現了
7
示意性實現代碼:
Charactor 對象,所以它還是應該出現在 Charactor 類中,對于不同的狀態可以采取在客戶程序中通過參數化的方式傳入。類結構圖如下:
public class Program
{
public static void Main()
{
Charactorca
= new CharactorA();
Charactorcb
= new CharactorB();
Charactorcc
= new CharactorC();

// 顯示字符

// 設置字符的大小ChangeSize();
}


public void ChangeSize()
{
// 在這里設置字符的大小
}

}

按照這樣的實現思路,可以發現如果有多個客戶端程序使用的話,會出現大量的重復性的邏輯,用重構的術語來說是出現了代碼的壞味道,不利于代碼的復用和維護;另外把這些狀態和行為移到客戶程序里面破壞了封裝性的原則。再次轉變我們的實現思路,可以確定的是這些狀態仍然屬于
8
示意性實現代碼:
Flyweight 模式實現了優化資源的這樣一個目的。在這個過程中,還有如下幾點需要說明:
// "Charactor"
public abstract class Charactor
{
// Fields
protected char _symbol;

protected int _width;

protected int _height;

protected int _ascent;

protected int _descent;

protected int _pointSize;

// Method
public abstract void SetPointSize( int size);
public abstract void Display();
}


// "CharactorA"
public class CharactorA:Charactor
{
// Constructor
public CharactorA()
{
this ._symbol = ' A ' ;
this ._height = 100 ;
this ._width = 120 ;
this ._ascent = 70 ;
this ._descent = 0 ;
}


// Method
public override void SetPointSize( int size)
{
this ._pointSize = size;
}


public override void Display()
{
Console.WriteLine(
this ._symbol +
" pointsize: " + this ._pointSize);
}

}


// "CharactorB"
public class CharactorB:Charactor
{
// Constructor
public CharactorB()
{
this ._symbol = ' B ' ;
this ._height = 100 ;
this ._width = 140 ;
this ._ascent = 72 ;
this ._descent = 0 ;
}


// Method
public override void SetPointSize( int size)
{
this ._pointSize = size;
}


public override void Display()
{
Console.WriteLine(
this ._symbol +
" pointsize: " + this ._pointSize);
}

}


// "CharactorC"
public class CharactorC:Charactor
{
// Constructor
public CharactorC()
{
this ._symbol = ' C ' ;
this ._height = 100 ;
this ._width = 160 ;
this ._ascent = 74 ;
this ._descent = 0 ;
}


// Method
public override void SetPointSize( int size)
{
this ._pointSize = size;
}


public override void Display()
{
Console.WriteLine(
this ._symbol +
" pointsize: " + this ._pointSize);
}

}


// "CharactorFactory"
public class CharactorFactory
{
// Fields
private Hashtablecharactors = new Hashtable();

// Constructor
public CharactorFactory()
{
charactors.Add(
" A " , new CharactorA());
charactors.Add(
" B " , new CharactorB());
charactors.Add(
" C " , new CharactorC());
}


// Method
public CharactorGetCharactor( string key)
{
Charactorcharactor
= charactors[key] as Charactor;

if (charactor == null )
{
switch (key)
{
case " A " :charactor = new CharactorA(); break ;
case " B " :charactor = new CharactorB(); break ;
case " C " :charactor = new CharactorC(); break ;
//
}

charactors.Add(key,charactor);
}

return charactor;
}

}


public class Program
{
public static void Main()
{
CharactorFactoryfactory
= new CharactorFactory();

// Charactor"A"
CharactorAca = (CharactorA)factory.GetCharactor( " A " );
ca.SetPointSize(
12 );
ca.Display();

// Charactor"B"
CharactorBcb = (CharactorB)factory.GetCharactor( " B " );
ca.SetPointSize(
10 );
ca.Display();

// Charactor"C"
CharactorCcc = (CharactorC)factory.GetCharactor( " C " );
ca.SetPointSize(
14 );
ca.Display();
}

}

可以看到這樣的實現明顯優于第一種實現思路。好了,到這里我們就到到了通過
1 .引入 CharactorFactory 是個關鍵,在這里創建對象已經不是 new 一個 Charactor 對象那么簡單,而必須用工廠方法封裝起來。
2 .在這個例子中把 Charactor 對象作為 Flyweight 對象是否準確值的考慮,這里只是為了說明 Flyweight 模式,至于在實際應用中,哪些對象需要作為 Flyweight 對象是要經過很好的計算得知,而絕不是憑空臆想。
3 .區分內外部狀態很重要,這是享元對象能做到享元的關鍵所在。
到這里,其實我們的討論還沒有結束。有人可能會提出如下問題,享元對象( Charactor )在這個系統中相對于每一個內部狀態而言它是唯一的,這跟單件模式有什么區別呢?這個問題已經很好回答了,那就是單件類是不能直接被實例化的,而享元類是可以被實例化的。事實上在這里面真正被設計為單件的應該是享元工廠(不是享元)類,因為如果創建很多個享元工廠的實例,那我們所做的一切努力都是白費的,并沒有減少對象的個數。修改后的類結構圖如下:
9
示意性實現代碼:
// "CharactorFactory"
public class CharactorFactory
{
// Fields
private Hashtablecharactors = new Hashtable();

private CharactorFactoryinstance;
// Constructor
private CharactorFactory()
{
charactors.Add(
" A " , new CharactorA());
charactors.Add(
" B " , new CharactorB());
charactors.Add(
" C " , new CharactorC());
}


// Property
public CharactorFactoryInstance
{
get
{
if (instance != null )
{
instance
= new CharactorFactory();
}

return instance;
}

}


// Method
public CharactorGetCharactor( string key)
{
Charactorcharactor
= charactors[key] as Charactor;

if (charactor == null )
{
switch (key)
{
case " A " :charactor = new CharactorA(); break ;
case " B " :charactor = new CharactorB(); break ;
case " C " :charactor = new CharactorC(); break ;
//
}

charactors.Add(key,charactor);
}

return charactor;
}

}

.NET 框架中的Flyweight
Flyweight 更多時候的時候一種底層的設計模式,在我們的實際應用程序中使用的并不是很多。在.NET中的String類型其實就是運用了Flyweight模式。可以想象,如果每次執行string s1 = “abcd”操作,都創建一個新的字符串對象的話,內存的開銷會很大。所以.NET中如果第一次創建了這樣的一個字符串對象s1,下次再創建相同的字符串s2時只是把它的引用指向“abcd”,這樣就實現了“abcd”在內存中的共享。可以通過下面一個簡單的程序來演示s1和s2的引用是否一致:
True 。但是大家要注意的是如果再有一個字符串 s3 ,它的初始值為“ ab ”,再對它進行操作 s3 = s3 + “cd” ,這時雖然 s1 s3 的值相同,但是它們的引用是不同的。關于 String 的詳細情況大家可以參考 SDK ,這里不再討論了。
public class Program
{
public static void Main( string []args)
{
string s1 = " abcd " ;
string s2 = " abcd " ;

Console.WriteLine(Object.ReferenceEquals(s1,s2));

Console.ReadLine();
}

}

可以看到,輸出的結果為
效果及實現要點
1 .面向對象很好的解決了抽象性的問題,但是作為一個運行在機器中的程序實體,我們需要考慮對象的代價問題。 Flyweight 設計模式主要解決面向對象的代價問題,一般不觸及面向對象的抽象性問題。
2 Flyweight 采用對象共享的做法來降低系統中對象的個數,從而降低細粒度對象給系統帶來的內存壓力。在具體實現方面,要注意對象狀態的處理。
3 享元模式的優點在于它大幅度地降低內存中對象的數量。但是,它做到這一點所付出的代價也是很高的:享元模式使得系統更加復雜。為了使對象可以共享,需要將一些狀態外部化,這使得程序的邏輯復雜化。另外它將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。
適用性
當以下所有的條件都滿足時,可以考慮使用享元模式:
1、 一個系統有大量的對象。
2、 這些對象耗費大量的內存。
3、 這些對象的狀態中的大部分都可以外部化。
4、 這些對象可以按照內蘊狀態分成很多的組,當把外蘊對象從對象中剔除時,每一個組都可以僅用一個對象代替。
5、 軟件系統不依賴于這些對象的身份,換言之,這些對象可以是不可分辨的。
滿足以上的這些條件的系統可以使用享元對象。最后,使用享元模式需要維護一個記錄了系統已有的所有享元的表,而這需要耗費資源。因此,應當在有足夠多的享元實例可供共享時才值得使用享元模式。
總結
Flyweight 模式解決的是由于大量的細粒度對象所造成的內存開銷的問題,它在實際的開發中并不常用,但是作為底層的提升性能的一種手段卻很有效。
參考資料
Erich Gamma 等,《設計模式:可復用面向對象軟件的基礎》,機械工業出版社
Robert C.Martin ,《敏捷軟件開發:原則、模式與實踐》,清華大學出版社
閻宏,《 Java 與模式》,電子工業出版社
Alan Shalloway James R. Trott ,《 Design Patterns Explained 》,中國電力出版社
MSDN WebCast C# 面向對象設計模式縱橫談 (12) Flyweight 享元模式 ( 結構型模式 )
http://www.dofactory.com/

(第Ⅲ部分 結構型模式篇) 第12章 享元模式(Flyweight Pattern)


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 久久综合给合久久97色美利坚 | 免费毛片一级 | 在线aa| 精品久久久久久久久免费影院 | 亚洲天天干 | 天天干视频网站 | 91视频网址入口 | 欧美黑人巨大xxxxxxxx | 草视频在线观看 | 久久精品国产欧美 | 久久成人亚洲 | 精品久久久久久中文字幕一区 | 99精品视频在线 | 国产精品福利资源在线 | 中文乱码在线观看 | 99免费在线观看视频 | 国产性生活视频 | 欧美三级aaa | 中文国产成人精品久久96 | 99香蕉国产精品偷在线观看 | 精品的一区二区三区 | 国产高清视频在线免费观看 | 2046影院视频大全在线观看 | 国产成人亚洲毛片 | 久操免费 | chinese456老人gay chinese国产xxxx中国 | 精品煌色视频网站在线观看 | 久久精品国产2020观看福利色 | 我爱52avαv永久网站 | 色偷偷91久久综合噜噜噜噜 | 国产在线精品成人一区二区三区 | 97午夜精品| 鲁丝丝国产一区二区 | 一本久道久综合久久鬼色 | 久久国产精品久久久久久久久久 | 国产综合另类小说色区色噜噜 | 久久久91精品国产一区二区三区 | 精品免费| 色鬼综合 | 久久九九精品视频 | 成人不卡 |