典型的享元模式的例子為文書處理器中以圖形結構來表示字元。一個做法是,每個字形有其字型外觀, 字模 metrics, 和其它格式資訊,但這會使每個字元就耗用上千位元組。取而代之的是,每個字元參照到一個共享字形物件,此物件會被其它有共同特質的字元所分享;只有每個字元(文件中或頁面中)的位置才需要另外儲存。
在軟體發展過程,如果需要重複使用某個物件的時候,如果重複地使用new創建這個物件的話,這樣在記憶體就需要多次地去申請記憶體空間了,這樣可能會出現記憶體使用越來越多的情況,這樣的問題是非常嚴重,然而享元模式可以解決這個問題,下面具體看看享元模式是如何去解決這個問題的。
優點:
- 降低了系統中物件的數量,從而降低了系統中細細微性物件給記憶體帶來的壓力。
缺點:
- 為了使物件可以共用,需要將一些狀態外部化,這使得程式的邏輯更複雜,使系統複雜化。
- 享元模式將享元物件的狀態外部化,而讀取外部狀態使得執行時間稍微變長。
Flyweight Pattern可以適用在下列的情況之下:
- 一個系統有大量的物件。
- 這些物件耗費大量的記憶體。
- 這些物件的狀態中的大部分都可以外部化。
- 這些物件可以按照內蘊狀態分成很多的組,當把外蘊物件從物件中剔除時,每一個組都可以僅用一個物件代替。
- 軟體系統不依賴於這些物件的身份。
享元模式需要維護一個記錄了系統已有的所有享元的表,而這需要耗費資源。因此,應當在有足夠多的享元實例可供共用時才值得使用享元模式。
Flyweight可以理解為共用元物件(細細微性物件)的意思,提到Flyweight模式都會一般都會用編輯器例子來說明。考慮這樣一個文字處理軟體,它需要處理的物件可能有單個的字元,由字元組成的段落以及整篇文檔,根據物件導向的設計思想和Composite模式,不管是字元還是段落,文檔都應該作為單個的對象去看待,這裡只考慮單個的字元,不考慮段落及文檔等物件,於是可以很容易的得到下面的結構圖:
想像一下,在一篇文檔中,字元的數量遠不止幾百個這麼簡單,可能上千上萬,記憶體中就同時存在了上千上萬個Charactor物件,這樣的記憶體開銷是可想而知的。進一步分析可以發現,雖然需要的Charactor實例非常多,但這些只不過是狀態不同而已,也就是說這些實例的狀態數量是很少的。所以並不需要這麼多的獨立的Charactor實例,而只需要為每一種Charactor狀態創建一個實例,讓整個字元處理軟體共用這些實例就可以了。示意圖:
A,B,C三個字元是共用的,也就是說如果文檔中任何地方需要這三個字元,只需要使用共用的這三個實例就可以了。然而發現單純的這樣共用也是有問題的。雖然文檔中的用到了很多的A字元,雖然字元的symbol等是相同的,它可以共用;但是它們的pointSize卻是不相同的,即字元在文檔中中的大小是不相同的,這個狀態不可以共用。為解決這個問題,首先我們將不可共用的狀態從類裡面剔除,暫時去掉pointSize這個狀態,結構圖如下所示:
public abstract class Charactor
{
protected char _symbol;
protected int _width;
protected int _height;
protected int _ascent;
protected int _descent;
public abstract void Display();
}
public class CharactorA : Charactor
{
public CharactorA()
{
this._symbol = 'A';
this._height = 100;
this._width = 120;
this._ascent = 70;
this._descent = 0;
}
public override void Display()
{
Console.WriteLine(this._symbol);
}
}
public class CharactorB : Charactor
{
public CharactorB()
{
this._symbol = 'B';
this._height = 100;
this._width = 140;
this._ascent = 72;
this._descent = 0;
}
public override void Display()
{
Console.WriteLine(this._symbol);
}
}
public class CharactorC : Charactor
{
public CharactorC()
{
this._symbol = 'C';
this._height = 100;
this._width = 160;
this._ascent = 74;
this._descent = 0;
}
public override void Display()
{
Console.WriteLine(this._symbol);
}
}
public class CharactorFactory
{
private Hashtable charactors = new Hashtable();
public CharactorFactory()
{
charactors.Add("A", new CharactorA());
charactors.Add("B", new CharactorB());
charactors.Add("C", new CharactorC());
}
public Charactor GetCharactor(string key)
{
Charactor charactor = 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;
}
}
到這裡完全解決了可以共用的狀態,下面的工作就是處理剛才被我們剔除出去的那些不可共用的狀態,因為雖然將那些狀態移除了,但是Charactor物件仍然需要這些狀態,被剝離後這些物件根本就無法工作,所以需要將這些狀態外部化。首先用比較簡單的解決方案就是對於不能共用的那些狀態,不需要去在Charactor類中設置,而直接在客戶程式碼中進行設置,類結構圖如下:
public class Program
{
public static void Main()
{
Charactor ca = new CharactorA();
Charactor cb = new CharactorB();
Charactor cc = new CharactorC();
}
public void ChangeSize() { }
}
如果有多個用戶端程式使用的話,會出現大量的重複性的邏輯,不利於代碼的複用和維護,另外把這些狀態和行為移到客戶程式裡面破壞了封裝性的原則。再次轉變我們的實現思路,可以確定的是這些狀態仍然屬於Charactor物件,所以它還是應該出現在Charactor類中,對於不同的狀態可以採取在客戶程式中通過參數化的方式傳入。類結構圖如下:
public abstract class Charactor
{
protected char _symbol;
protected int _width;
protected int _height;
protected int _ascent;
protected int _descent;
protected int _pointSize;
public abstract void SetPointSize(int size);
public abstract void Display();
}
public class CharactorA : Charactor
{
public CharactorA()
{
this._symbol = 'A';
this._height = 100;
this._width = 120;
this._ascent = 70;
this._descent = 0;
}
public override void SetPointSize(int size)
{
this._pointSize = size;
}
public override void Display()
{
Console.WriteLine(this._symbol +
"pointsize:" + this._pointSize);
}
}
public class CharactorB : Charactor
{
public CharactorB()
{
this._symbol = 'B';
this._height = 100;
this._width = 140;
this._ascent = 72;
this._descent = 0;
}
public override void SetPointSize(int size)
{
this._pointSize = size;
}
public override void Display()
{
Console.WriteLine(this._symbol +
"pointsize:" + this._pointSize);
}
}
public class CharactorC : Charactor
{
public CharactorC()
{
this._symbol = 'C';
this._height = 100;
this._width = 160;
this._ascent = 74;
this._descent = 0;
}
public override void SetPointSize(int size)
{
this._pointSize = size;
}
public override void Display()
{
Console.WriteLine(this._symbol +
"pointsize:" + this._pointSize);
}
}
public class CharactorFactory
{
private Hashtable charactors = new Hashtable();
public CharactorFactory()
{
charactors.Add("A", new CharactorA());
charactors.Add("B", new CharactorB());
charactors.Add("C", new CharactorC());
}
public Charactor GetCharactor(string key)
{
Charactor charactor = 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()
{
CharactorFactory factory = new CharactorFactory();
CharactorA ca =
(CharactorA)factory.GetCharactor("A");
ca.SetPointSize(12);
ca.Display();
CharactorB cb =
(CharactorB)factory.GetCharactor("B");
ca.SetPointSize(10);
ca.Display();
CharactorC cc = (CharactorC)factory.GetCharactor("C");
ca.SetPointSize(14);
ca.Display();
}
}
可以看到這樣的實現明顯優於第一種實現思路,Flyweight模式實現了優化資源的這樣一個目的。在這個過程中,還有如下幾點需要說明:
- 引入CharactorFactory是個關鍵,在這裡創建物件已經不是new一個Charactor物件那麼簡單,而必須用工廠方法封裝起來。
- 在這個例子中把Charactor物件作為Flyweight物件是否準確值的考慮,這裡只是為了說明Flyweight模式,至於在實際應用中,哪些物件需要作為Flyweight物件是要經過很好的計算得知。
- 區分內外部狀態很重要,這是Flyweight物件能做到享元的關鍵所在。
-雲遊山水為知已逍遙一生而忘齡- 電腦神手
沒有留言:
張貼留言