日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

(第Ⅲ部分 結(jié)構(gòu)型模式篇) 第12章 享元模式(Fly

系統(tǒng) 3088 0
——.NET設(shè)計(jì)模式系列之十三
Terrylee ,2006年3月
摘要: 面向?qū)ο蟮乃枷牒芎玫亟鉀Q了抽象性的問題,一般也不會出現(xiàn)性能上的問題。但是在某些情況下,對象的數(shù)量可能會太多,從而導(dǎo)致了運(yùn)行時的代價。那么我們?nèi)绾稳ケ苊獯罅考?xì)粒度的對象,同時又不影響客戶程序使用面向?qū)ο蟮姆绞竭M(jìn)行操作?
本文試圖通過一個簡單的字符處理的例子,運(yùn)用重構(gòu)的手段,一步步帶你走進(jìn)Flyweight模式,在這個過程中我們一同思考、探索、權(quán)衡,通過比較而得出好的實(shí)現(xiàn)方式,而不是給你最終的一個完美解決方案。
主要內(nèi)容:
1. Flyweight 模式解說
2 ..NET中的Flyweight模式
3 .Flyweight模式的實(shí)現(xiàn)要點(diǎn)
……
概述
面向?qū)ο蟮乃枷牒芎玫亟鉀Q了抽象性的問題,一般也不會出現(xiàn)性能上的問題。但是在某些情況下,對象的數(shù)量可能會太多,從而導(dǎo)致了運(yùn)行時的代價。那么我們?nèi)绾稳ケ苊獯罅考?xì)粒度的對象,同時又不影響客戶程序使用面向?qū)ο蟮姆绞竭M(jìn)行操作?
意圖
運(yùn)用共享技術(shù)有效地支持大量細(xì)粒度的對象。 [GOF 《設(shè)計(jì)模式》 ]
結(jié)構(gòu)圖
1Flyweight 模式結(jié)構(gòu)圖
生活中的例子
享元模式使用共享技術(shù)有效地支持大量細(xì)粒度的對象。公共交換電話網(wǎng)( PSTN )是享元的一個例子。有一些資源例如撥號音發(fā)生器、振鈴發(fā)生器和撥號接收器是必須由所有用戶共享的。當(dāng)一個用戶拿起聽筒打電話時,他不需要知道使用了多少資源。對于用戶而言所有的事情就是有撥號音,撥打號碼,撥通電話。
2 使用撥號音發(fā)生器例子的享元模式對象圖
Flyweight 模式解說
Flyweight 在拳擊比賽中指最輕量級,即“蠅量級”,這里翻譯為“享元”,可以理解為共享元對象(細(xì)粒度對象)的意思。提到Flyweight模式都會一般都會用編輯器例子來說明,這里也不例外,但我會嘗試著通過重構(gòu)來看待Flyweight模式。考慮這樣一個字處理軟件,它需要處理的對象可能有單個的字符,由字符組成的段落以及整篇文檔,根據(jù)面向?qū)ο蟮脑O(shè)計(jì)思想和Composite模式,不管是字符還是段落,文檔都應(yīng)該作為單個的對象去看待, 這里只考慮單個的字符,不考慮段落及文檔等對象, 于是可以很容易的得到下面的結(jié)構(gòu)圖:
圖3
示意性實(shí)現(xiàn)代碼:
Charactor 對象,這樣的內(nèi)存開銷是可想而知的。進(jìn)一步分析可以發(fā)現(xiàn),雖然我們需要的 Charactor 實(shí)例非常多,這些實(shí)例之間只不過是狀態(tài)不同而已,也就是說這些實(shí)例的狀態(tài)數(shù)量是很少的。所以我們并不需要這么多的獨(dú)立的 Charactor 實(shí)例,而只需要為每一種 Charactor 狀態(tài)創(chuàng)建一個實(shí)例,讓整個字符處理軟件共享這些實(shí)例就可以了。看這樣一幅示意圖:
// "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);
}

}

好了,現(xiàn)在看到的這段代碼可以說是很好地符合了面向?qū)ο蟮乃枷耄峭瑫r我們也為此付出了沉重的代價,那就是性能上的開銷,可以想象,在一篇文檔中,字符的數(shù)量遠(yuǎn)不止幾百個這么簡單,可能上千上萬,內(nèi)存中就同時存在了上千上萬個
4
現(xiàn)在我們看到的 A B C 三個字符是共享的,也就是說如果文檔中任何地方需要這三個字符,只需要使用共享的這三個實(shí)例就可以了。然而我們發(fā)現(xiàn)單純的這樣共享也是有問題的。雖然文檔中的用到了很多的 A 字符,雖然字符的 symbol 是相同的,它可以共享;但是它們的 pointSize 卻是不相同的,即字符在文檔中中的大小是不相同的,這個狀態(tài)不可以共享。為解決這個問題,首先我們將不可共享的狀態(tài)從類里面剔除出去,即去掉 pointSize 個狀態(tài)(只是暫時的 J ),類結(jié)構(gòu)圖如下所示:
5
示意性實(shí)現(xiàn)代碼:
Charactor 類的創(chuàng)建過程,即如果已經(jīng)存在了“ A ”字符這樣的實(shí)例,就不需要再創(chuàng)建,直接返回實(shí)例;如果沒有,則創(chuàng)建一個新的實(shí)例。如果把這項(xiàng)工作交給 Charactor 類,即 Charactor 類在負(fù)責(zé)它自身職責(zé)的同時也要負(fù)責(zé)管理 Charactor 實(shí)例的管理工作,這在一定程度上有可能違背類的單一職責(zé)原則,因此,需要一個單獨(dú)的類來做這項(xiàng)工作,引入 CharactorFactory 類,結(jié)構(gòu)圖如下:
// "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);
}

}

好,現(xiàn)在類里面剩下的狀態(tài)都可以共享了,下面我們要做的工作就是控制
6
示意性實(shí)現(xiàn)代碼: switch 語句,但這可以通過別的辦法消除,為了簡單期間我們先保持這種寫法)。下面的工作就是處理剛才被我們剔除出去的那些不可共享的狀態(tài),因?yàn)殡m然將那些狀態(tài)移除了,但是 Charactor 對象仍然需要這些狀態(tài),被我們剝離后這些對象根本就無法工作,所以需要將這些狀態(tài)外部化。首先會想到一種比較簡單的解決方案就是對于不能共享的那些狀態(tài),不需要去在 Charactor 類中設(shè)置,而直接在客戶程序代碼中進(jìn)行設(shè)置,類結(jié)構(gòu)圖如下:
// "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;
}

}

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

// 顯示字符

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


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

}

按照這樣的實(shí)現(xiàn)思路,可以發(fā)現(xiàn)如果有多個客戶端程序使用的話,會出現(xiàn)大量的重復(fù)性的邏輯,用重構(gòu)的術(shù)語來說是出現(xiàn)了代碼的壞味道,不利于代碼的復(fù)用和維護(hù);另外把這些狀態(tài)和行為移到客戶程序里面破壞了封裝性的原則。再次轉(zhuǎn)變我們的實(shí)現(xiàn)思路,可以確定的是這些狀態(tài)仍然屬于
8
示意性實(shí)現(xiàn)代碼:
Flyweight 模式實(shí)現(xiàn)了優(yōu)化資源的這樣一個目的。在這個過程中,還有如下幾點(diǎn)需要說明:
// "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();
}

}

可以看到這樣的實(shí)現(xiàn)明顯優(yōu)于第一種實(shí)現(xiàn)思路。好了,到這里我們就到到了通過
1 .引入 CharactorFactory 是個關(guān)鍵,在這里創(chuàng)建對象已經(jīng)不是 new 一個 Charactor 對象那么簡單,而必須用工廠方法封裝起來。
2 .在這個例子中把 Charactor 對象作為 Flyweight 對象是否準(zhǔn)確值的考慮,這里只是為了說明 Flyweight 模式,至于在實(shí)際應(yīng)用中,哪些對象需要作為 Flyweight 對象是要經(jīng)過很好的計(jì)算得知,而絕不是憑空臆想。
3 .區(qū)分內(nèi)外部狀態(tài)很重要,這是享元對象能做到享元的關(guān)鍵所在。
到這里,其實(shí)我們的討論還沒有結(jié)束。有人可能會提出如下問題,享元對象( Charactor )在這個系統(tǒng)中相對于每一個內(nèi)部狀態(tài)而言它是唯一的,這跟單件模式有什么區(qū)別呢?這個問題已經(jīng)很好回答了,那就是單件類是不能直接被實(shí)例化的,而享元類是可以被實(shí)例化的。事實(shí)上在這里面真正被設(shè)計(jì)為單件的應(yīng)該是享元工廠(不是享元)類,因?yàn)槿绻麆?chuàng)建很多個享元工廠的實(shí)例,那我們所做的一切努力都是白費(fèi)的,并沒有減少對象的個數(shù)。修改后的類結(jié)構(gòu)圖如下:
9
示意性實(shí)現(xiàn)代碼:
// "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 更多時候的時候一種底層的設(shè)計(jì)模式,在我們的實(shí)際應(yīng)用程序中使用的并不是很多。在.NET中的String類型其實(shí)就是運(yùn)用了Flyweight模式。可以想象,如果每次執(zhí)行string s1 = “abcd”操作,都創(chuàng)建一個新的字符串對象的話,內(nèi)存的開銷會很大。所以.NET中如果第一次創(chuàng)建了這樣的一個字符串對象s1,下次再創(chuàng)建相同的字符串s2時只是把它的引用指向“abcd”,這樣就實(shí)現(xiàn)了“abcd”在內(nèi)存中的共享。可以通過下面一個簡單的程序來演示s1和s2的引用是否一致:
True 。但是大家要注意的是如果再有一個字符串 s3 ,它的初始值為“ ab ”,再對它進(jìn)行操作 s3 = s3 + “cd” ,這時雖然 s1 s3 的值相同,但是它們的引用是不同的。關(guān)于 String 的詳細(xì)情況大家可以參考 SDK ,這里不再討論了。
public class Program
{
public static void Main( string []args)
{
string s1 = " abcd " ;
string s2 = " abcd " ;

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

Console.ReadLine();
}

}

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

(第Ⅲ部分 結(jié)構(gòu)型模式篇) 第12章 享元模式(Flyweight Pattern)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

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

【本文對您有幫助就好】

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

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 合川市| 榆林市| 策勒县| 即墨市| 敖汉旗| 苍南县| 桓仁| 靖江市| 东辽县| 浪卡子县| 河南省| 华容县| 临西县| 治县。| 定州市| 延吉市| 宁蒗| 潍坊市| 韶山市| 澄城县| 桃园县| 双鸭山市| 松滋市| 江油市| 古丈县| 伊宁县| 屯门区| 玛纳斯县| 宝坻区| 石家庄市| 太仆寺旗| 三明市| 奈曼旗| 湟源县| 平度市| 巨鹿县| 成都市| 卢龙县| 夹江县| 广丰县| 太仓市|