在軟件開發過程,如果我們需要重復使用某個對象的時候,如果我們重復地使用new創建這個對象的話,這樣我們在內存就需要多次地去申請內存空間了,這樣可能會出現內存使用越來越多的情況,這樣的問題是非常嚴重,然而享元模式可以解決這個問題,下面具體看看享元模式是如何去解決這個問題的。
一、 享元(Flyweight)模式
在前面說了,享元模式可以解決上面的問題了,在介紹享元模式之前,讓我們先要分析下如果去解決上面那個問題,上面的問題就是重復創建了同一個對象,如果讓我們去解決這個問題肯定會這樣想:“既然都是同一個對象,能不能只創建一個對象,然後下次需要創建這個對象的時候,讓它直接用已經創建好了的對象就好了”,也就是說——讓一個對象共享。不錯,這個也是享元模式的實現精髓所在。
介紹完享元模式的精髓之後,讓我們具體看看享元模式的正式定義:
享元模式——運用共享技術有效地支持大量細粒度的對象。享元模式可以避免大量相似類的開銷,在軟件開發中如果需要生成大量細粒度的類實例來表示數據,如果這些實例除了幾個參數外基本上都是相同的,這時候就可以使用享元模式來大幅度減少需要實例化類的數量。如果能把這些參數(指的這些類實例不同的參數)移動類實例外面,在方法調用時將他們傳遞進來,這樣就可以通過共享大幅度地減少單個實例的數目。(這個也是享元模式的實現要領),然而我們把類實例外面的參數稱為享元對象的外部狀態,把在享元對象內部定義稱為內部狀態。具體享元對象的內部狀態與外部狀態的定義為:
內部狀態:在享元對象的內部並且不會隨著環境的改變而改變的共享部分
外部狀態:隨環境改變而改變的,不可以共享的狀態。
二、 享元模式的類圖
在上圖中,涉及的角色如下幾種角色:
注:上面的實現只是單純的享元模式,同時還有復合的享元模式,由於復合享元模式較復雜,這裡就不給出實現了。
三、 享元模式的實現
下面以一個實際的應用來實現下享元模式。這個例子是:一個文本編輯器中會出現很多字面,使用享元模式去實現這個文本編輯器的話,會把每個字面做成一個享元對象。享元對象的內部狀態就是這個字面,而字母在文本中的位置和字體風格等其他信息就是它的外部狀態。下面就以這個例子來實現下享元模式,具體實現代碼如下:
using System;
using System.Collections;
/// <summary>
/// 客戶端調用
/// </summary>
class Client
{
static void Main(string[] args)
{
// 定義外部狀態,例如字母的位置等信息
int externalstate = 10;
// 初始化享元工廠
var 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");
// 這時候就需要創建一個對象並放入駐留池中
var 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 readonly 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);
}
}
在享元模式的實現中,我們沒有像之前一樣,把一個細粒度的類實例設計成一個單獨的類,而是把它作為共享對象的內部狀態放在共享類的內部定義,具體的解釋注釋中都有了,大家可以參考注釋去進一步理解享元模式。
四、 使用場景
在下面所有條件都滿足時,可以考慮使用享元模式:
滿足上面的條件的系統可以使用享元模式。但是使用享元模式需要額外維護一個記錄子系統已有的所有享元的表,而這也需要耗費資源,所以,應當在有足夠多的享元實例可共享時才值得使用享元模式。
注:在.NET類庫中,string類的實現就使用了享元模式。
五、 享元模式的優缺點
優點:
缺點:
六、 總結
到這裡,享元模式的介紹就結束了,享元模式主要用來解決由於大量的細粒度對象所造成的內存開銷的問題,它在實際的開發中並不常用,可以作為底層的提升性能的一種手段。