類別關係圖
類角色說明
抽象享元(Flyweight)角色:此角色是所有的具體享元類的超類,為這些類規定出需要實現的公用介面。那些需要外蘊狀態(External State)的操作可以通過調用商業方法以參數形式傳入。
具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定的介面。如果有內蘊狀態的話,必須負責為內蘊狀態提供儲存空間。享元對象的內蘊狀態必須與對象所處的周圍環境無關,從而使得享元對象可以在系統內共用的。
享元工廠(FlyweightFactory)角色:本角色負責建立和管理享元角色。本角色必須保證享元對象可以被系統適當地共用。當一個用戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個複合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當建立一個合適的享元對象。
用戶端(Client)角色:本角色需要維護一個對所有享元對象的引用。本角色需要自行儲存所有享元對象的外蘊狀態。
意圖
Flyweight 在拳擊比賽中指最輕量級,即"蠅量級",有些作者翻譯為"羽量級"。這裡使用"享元模式"更能反映模式的用意。
享元模式以共用的方式高效地支援大量的細粒度對象。享元對象能做到共用的關鍵是區分內蘊狀態(Internal State)和外蘊狀態(External State)。內蘊狀態是儲存在享元對象內部並且不會隨環境改變而改變。因此內蘊狀態並可以共用。 外蘊狀態是隨環境改變而改變的、不可以共用的狀態。享元對象的外蘊狀態必須由用戶端儲存,並在享元對象被建立之後,在需要使用的時候再傳入到享元對象內部。外蘊狀態與內蘊狀態是相互獨立的。
適用性
當以下所有的條件都滿足時,可以考慮使用享元模式:
· 一個系統有大量的對象。
· 這些對象耗費大量的記憶體。
· 這些對象的狀態中的大部分都可以外部化。
· 這些對象可以按照內蘊狀態分成很多的組,當把外蘊對象從對象中剔除時,每一個組都可以僅用一個對象代替。
· 軟體系統不依賴於這些對象的身份,換言之,這些對象可以是不可分辨的。
滿足以上的這些條件的系統可以使用享元對象。
最後,使用享元模式需要維護一個記錄了系統已有的所有享元的表,而這需要耗費資源。因此,應當在有足夠多的享元執行個體可供共用時才值得使用享元模式。
泡妞的例子
FLYWEIGHT —每天跟MM發簡訊,手指都累死了,最近買了個新手機,可以把一些常用的句子存在手機裡,要用的時候,直接拿出來,在前面加上MM的名字就可以發送了,再不用一個字一個字敲了。共用的句子就是Flyweight,MM的名字就是提取出來的外部特徵,根據上下文情況使用。
泡妞的代碼
using System;
using System.Collections;
//抽象享元(Flyweight)角色
public abstract class 抽象簡訊
{
public abstract void 發送();
}
//實際享元角色 如果有內蘊狀態的話,必須負責為內蘊狀態提供儲存空間。享元對象的內蘊狀態必須與對象所處的周圍環境無關,從而使得享元對象可以在系統內共用的。
public class 簡訊:抽象簡訊
{
string msg;
public 簡訊(string strmsg)
{
msg=strmsg;
}
public override void 發送()
{
Console.WriteLine(msg);
}
}
//享元工廠類當一個用戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個複合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當建立一個合適的享元對象。
public class 手機
{
private Hashtable 簡訊模版 = new Hashtable();
//以內蘊狀態為參數檢查工廠列表中是否含有具有相同內蘊狀態的對象,有則返回該對象,否則建立新對象
public 抽象簡訊 GetMSG(string msg)
{
if(! 簡訊模版.ContainsKey(msg))
簡訊模版.Add(msg, new 簡訊(msg));
return ((抽象簡訊)簡訊模版[msg]);
}
public int 模版容量()
{
return 簡訊模版.Count;
}
}
//本角色需要維護一個對所有享元對象的引用。本角色需要自行儲存所有享元對象的外蘊狀態
public class Client
{
private static 手機 新手機;
private static int msgcount = 0; //自行儲存的外蘊狀態
public static void Main()
{
新手機=new 手機();
發簡訊("Lily","我愛你啊!");
發簡訊("Mary","我們做好朋友吧!");
發簡訊("Lisa","今晚有空麼?");
發簡訊("Mary","我愛你啊!");
Console.WriteLine("已發送"+msgcount.ToString()+"條簡訊息!");
Console.WriteLine("手機中共有"+Convert.ToString(新手機.模版容量())+"條模版資訊!");
}
private static void 發簡訊(string mm,string msg)
{
抽象簡訊 簡訊息 = 新手機.GetMSG(msg);
Console.Write(mm);
簡訊息.發送();
msgcount++;
}
}
享元模式
享元模式:FLYWEIGHT在拳擊比賽中指最輕量級。享元模式以共用的方式高效的支援大量的細粒度對象。享元模式能做到共用的關鍵是區分內蘊狀態和外蘊狀態。內蘊狀態儲存在享元內部,不會隨環境的改變而有所不同。外蘊狀態是隨環境的改變而改變的。外蘊狀態不能影響內蘊狀態,它們是相互獨立的。將可以共用的狀態和不可以共用的狀態從常規類中區分開來,將不可以共用的狀態從類裡剔除出去。用戶端不可以直接建立被共用的對象,而應當使用一個工廠對象負責建立被共用的對象。享元模式大幅度的降低記憶體中對象的數量。
優缺點
享元模式的優點在於它大幅度地降低記憶體中對象的數量。但是,它做到這一點所付出的代價也是很高的:
· 享元模式使得系統更加複雜。為了使對象可以共用,需要將一些狀態外部化,這使得程式的邏輯複雜化。
· 享元模式將享元對象的狀態外部化,而讀取外部狀態使得已耗用時間稍微變長。
說明
事物的可以被分為內部狀態和外部狀態。內部狀態標識對象的內部特性,保持相對穩定;而外部狀態實現對象的外部功能,隨著外部環境的變化而改變。對象的內部狀態和外部狀態相對獨立,不會互相影響。
這裡再說幾個享元模式應用的例子,都是網上很流行的例子
咖啡攤的例子
咖啡攤(Coffee Stall)所使用的系統裡,有一系列的咖啡"風味(Flavor)"。客人到攤位上購買咖啡,所有的咖啡均放在檯子上,客人自己拿到咖啡後就離開攤位。咖啡有內蘊狀態,也就是咖啡的風味;咖啡沒有環境因素,也就是說沒有外蘊狀態。如果系統為每一杯咖啡都建立一個獨立的對象的話,那麼就需要建立出很多的細小對象來。這樣就不如把咖啡按照種類(即"風味")劃分,每一種風味的咖啡只建立一個對象,並實行共用。
使用咖啡攤主的語言來講,所有的咖啡都可按"風味"劃分成如Capucino、Espresso等,每一種風味的咖啡不論賣出多少杯,都是全同、不可分辨的。所謂共用,就是咖啡風味的共用,製造方法的共用等。因此,享元模式對咖啡攤來說,就意味著不需要為每一份單獨調製。攤主可以在
需要時,一次性地調製出足夠一天出售的某一種風味的咖啡。
編輯器的例子
在編輯器系統中大量使用。一個文字編輯器往往會提供很多種字型,而通常的做法就是將每一個字母做成一個享元對象。享元對象的內蘊狀態就是這個字母,而字母在文本中的位置和字模風格等其他資訊則是外蘊狀態。比如,字母 a可能出現在文本的很多地方,雖然這些字母 a
的位置和字模風格不同,但是所有這些地方使用的都是同一個字母對象。這樣一來,字母對象就可以在整個系統中共用。
員工對象的例子
一個員工對象,其名字、性別等個人情況相對穩定,屬於員工對象的內部狀態,其工作角色,職務,薪酬等隨環境變化,屬於外部狀態。
通俗的解釋:
抽象享元(Flyweight)角色:定義員工對象基本的行為
實際享元角色:有內部狀態屬性,在初始化的時候對其內部屬性進行賦值
享元工廠角色:管理實際享元角色,如果用戶端需要建立一個實際享元角色,那麼先檢查是否已經有具有相同內部屬性的實際享元角色存在,如果存在則返回已存在的對象,否則則建立。