一、概述
在軟件開發中,我們有時需要創建大量細粒度的對象,比如文檔處理系統就可能需要創建成千上萬的字符對象。但如果對每個字符對象都分配內存,那麼在系統運行時就會耗費大量的內存。如何在保留面向對象操作方式優點的同時避免創建大量的對象呢?這就到了享元模式發揮作用的時候了。
二、享元模式
享元模式運用共享技術有效地支持大量細粒度的對象。例如可以對文檔處理系統創建共享池,在共享池中建立字母和代碼的對應關系,這樣就可以用共享池中的26個對象解決需要創建大量對象的問題。其結構圖如下:
Flyweight定義了享元接口,外部對象通過這個接口來訪問具體的享元對象。
ConcreteFlyweight實現Flyweight接口,定義了具體的享元對象,並保存享元對象的內部狀態。該享元對象是可共享的。
UnsharedConcreteFlyweight實現Flyweight接口,定義了不用於共享的享元對象。
FlyweightFactory創建並管理享元對象。
ClIEnt保存對享元接口的引用,通過該引用有效的使用具體的享元對象。
三、示例
下面以一個實際的應用來實現下享元模式。這個例子是:一個文本編輯器中會出現很多字面,使用享元模式去實現這個文本編輯器的話,會把每個字面做成一個享元對象。享元對象的內部狀態就是這個字面,而字母在文本中的位置和字體風格等其他信息就是它的外部狀態。下面就以這個例子來實現下享元模式,具體實現代碼如下:
/// <summary>
/// 客戶端調用
/// </summary>
class
ClIEnt
{
static
void
Main(
string
[] args)
{
// 定義外部狀態,例如字母的位置等信息
int
externalstate = 10;
// 初始化享元工廠
FlyweightFactory factory =
new
FlyweightFactory();
// 判斷是否已經創建了字母A,如果已經創建就直接使用創建的對象A
Flyweight fa = factory.GetFlyweight(
"A"
);
if
(fa !=
null
)
{
// 把外部狀態作為享元對象的方法調用參數
fa.Operation(--externalstate);
}
// 判斷是否已經創建了字母B
Flyweight fb = factory.GetFlyweight(
"B"
);
if
(fb !=
null
)
{
fb.Operation(--externalstate);
}
// 判斷是否已經創建了字母C
Flyweight fc = factory.GetFlyweight(
"C"
);
if
(fc !=
null
)
{
fc.Operation(--externalstate);
}
// 判斷是否已經創建了字母D
Flyweight fd= factory.GetFlyweight(
"D"
);
if
(fd !=
null
)
{
fd.Operation(--externalstate);
}
else
{
Console.WriteLine(
"駐留池中不存在字符串D"
);
// 這時候就需要創建一個對象並放入駐留池中
ConcreteFlyweight d =
new
ConcreteFlyweight(
"D"
);
factory.flyweights.Add(
"D"
, d);
}
Console.Read();
}
}
/// <summary>
/// 享元工廠,負責創建和管理享元對象
/// </summary>
public
class
FlyweightFactory
{
// 最好使用泛型Dictionary<string,Flyweighy>
//public Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
public
Hashtable flyweights =
new
Hashtable();
public
FlyweightFactory()
{
flyweights.Add(
"A"
,
new
ConcreteFlyweight(
"A"
));
flyweights.Add(
"B"
,
new
ConcreteFlyweight(
"B"
));
flyweights.Add(
"C"
,
new
ConcreteFlyweight(
"C"
));
}
public
Flyweight GetFlyweight(
string
key)
{
// 更好的實現如下
//Flyweight flyweight = flyweights[key] as Flyweight;
//if (flyweight == null)
//{
// Console.WriteLine("駐留池中不存在字符串" + key);
// flyweight = new ConcreteFlyweight(key);
//}
//return flyweight;
return
flyweights[key]
as
Flyweight;
}
}
/// <summary>
/// 抽象享元類,提供具體享元類具有的方法
/// </summary>
public
abstract
class
Flyweight
{
public
abstract
void
Operation(
int
extrinsicstate);
}
// 具體的享元對象,這樣我們不把每個字母設計成一個單獨的類了,而是作為把共享的字母作為享元對象的內部狀態
public
class
ConcreteFlyweight : Flyweight
{
// 內部狀態
private
string
intrinsicstate ;
// 構造函數
public
ConcreteFlyweight(
string
innerState)
{
this
.intrinsicstate = innerState;
}
/// <summary>
/// 享元類的實例方法
/// </summary>
/// <param name="extrinsicstate">外部狀態</param>
public
override
void
Operation(
int
extrinsicstate)
{
Console.WriteLine(
"具體實現類: intrinsicstate {0}, extrinsicstate {1}"
, intrinsicstate, extrinsicstate);
}
}
在享元模式的實現中,我們沒有像之前一樣,把一個細粒度的類實例設計成一個單獨的類,而是把它作為共享對象的內部狀態放在共享類的內部定義。
四、享元模式的優缺點
分析完享元模式的實現之後,讓我們繼續分析下享元模式的優缺點:
優點:
降低了系統中對象的數量,從而降低了系統中細粒度對象給內存帶來的壓力。
缺點:
1.為了使對象可以共享,需要將一些狀態外部化,這使得程序的邏輯更復雜,使系統復雜化。
2.享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。
五、使用場景
在下面所有條件都滿足時,可以考慮使用享元模式:
滿足上面的條件的系統可以使用享元模式。但是使用享元模式需要額外維護一個記錄子系統已有的所有享元的表,而這也需要耗費資源,所以,應當在有足夠多的享元實例可共享時才值得使用享元模式。