設計模式筆記(12)---享元模式(結構型)

Gof定義

運用共享技術有效地支持大量細粒度的對象。

動機

採用純粹對象方案的問題在於大量細粒度的對象會很快充斥在系統中,從而帶來很高的運行時代價——主要指內存需求方面的代價。如何在避免大量細粒度對象問題的同時,讓外部客戶程序仍然能夠透明地使用面向對象的方式來進行操作?這需要用到享元模式,不過應用享元模式是需要進行評估的,也就是說在當前情況下是否會對系統造成性能上的影響,如果會那麼就是用,下面先來看個小例子是如何進行評估的。

假設有一個字符的類Charator,如下:

public class Charator
{
    char c; 
    Font f;
}

爲了方便評估,Charator類中的Font類型採用自定義的類型:

public class Font
{
    string fontName;
    int size;
    Color color;
}

在客戶代碼中使用Charator類

public class App
{
    static void Main()
    {
        List<Charator> list=new List<Charator>(100000);
        for (int i = 0; i < list.Count; i++)
        {
            Charator charator = new Charator();
            list.Add(charator);
        }
    }
}

 

在客戶代碼中將Charator類實例化了100000次,Charator類中有兩個成員,並且有個成員爲Font類型,Font類型中又定義了三個成員,下面將在代碼註釋中標出要佔用的內存量:

/// <summary>
///  如果有n個Font對象佔用大小爲12bytes*n 其實字符串類型
///  除了本身佔用的4bytes外 還有一個引用指針要佔用4bytes,
///  不過這個 不會構成倍乘效應,也就是當有n個對象時不會
///  以n的倍數增長,所以可以忽略 
/// </summary>
public class Font
{
    string fontName; //4bytes
    int size;        //4bytes
    Color color;     //4bytes
}
/// <summary>
/// 總共佔用2+20+4+8+2=36bytes
/// 第三個的4爲指向font的指針
/// 第四個的8爲虛表指針和垃圾回收和同步
/// 最後的2爲char的填充位
/// </summary>
public class Charator
{
    /// <summary>
    /// 佔用2bytes
    /// </summary>
    char c; 
    /// <summary>
    /// 總佔用12bytes+4bytes+4bytes =20bytes
    /// 其中12bytes是三個成員的,另外的兩個4bytes分別爲
    /// 虛表指針和net垃圾回收機制所需要的一些位,因爲每個
    /// 類型最終都可以追溯到object類型,所以默認都會有虛函數
    /// 4byte的虛表指針式不可少的
    /// </summary>
    Font f; 
    
}
/// <summary>
/// 36bytes * 100000=3600000bytes =3600k=3.6mb
/// </summary>
public class App
{
    static void Main()
    {
        List<Charator> list=new List<Charator>(100000);
        for (int i = 0; i < list.Count; i++)
        {
            Charator charator = new Charator();
            list.Add(charator);
        }
    }
}

上面的代碼執行會帶來3mb多的內存數據,這個對於現在的機器來說算不了什麼,上面例子中是做了100000次的計算,那如果再加兩個數量級到10000000又會怎麼樣了,那就會有300多mb,這個數字肯定是不能接受的,那麼這個時候就要考慮用享元模式了。 看下面改進後的代碼:

public class Font
{
    string _fontName; 
    int _size;        
    Color _color;
    public Font(string name, int size, Color color)
    {
        _fontName = name;
        _size = size;
        _color = color;
    }
}

public class Charator
{
    private static Hashtable fontTable=new Hashtable();
    public char C { get; set; }
    public Font CFont { get; set; }
  
    public Font GetFont(string name)
    {
        if (!fontTable.ContainsKey(name))
        {
            fontTable.Add(name, new Font(name, 8, Color.Red));
        }
        return (Font)fontTable[name];
    }
}
public class App
{
    static void Main()
    {
        List<Charator> list = new List<Charator>(100000);
        for (int i = 0; i < list.Count; i++)
        {
            Charator charator = new Charator();
            charator.C = 'a';
            charator.CFont = charator.GetFont("宋體");
            list.Add(charator);
        }
    }
}

上面的代碼主要是在GetFont方法中進行了判斷,如果對象不存在才創建新的實例,否則直接返回存儲在HashTable中的對象。下面來看下享元模式的結構圖:

2009-12-03_224557 

針對上面結構的完整代碼:

/// <summary>
/// 享元的抽象類
/// </summary>
public abstract class Flyweight
{ 
    public abstract void Operation(int extrinsicState);
}
/// <summary>
/// 需要共享的具體類
/// </summary>
public class ConceteFlyweight : Flyweight
{
    public override void Operation(int extrinsicState)
    {
        Console.WriteLine("需要共享的具體Flyweight類:" + extrinsicState);
    }
}
/// <summary>
/// 不需要共享的具體類
/// </summary>
public class UnsharedConcreteFlyeight : Flyweight
{
    public override void Operation(int extrinsicState)
    {
        Console.WriteLine("不需要共享的具體Flyweight類:" + extrinsicState);
    }
}
/// <summary>
/// 一個工廠類,用來合理創建對象
/// </summary>
public class FlyweightFactory
{
    private Dictionary<string, Flyweight> dic = new Dictionary<string, Flyweight>();
    public Flyweight GetFlyweight(string key,bool type)
    {
        if (!dic.ContainsKey(key))
        {
            Flyweight flyweight = new UnsharedConcreteFlyeight();
            if (type)
                flyweight = new ConceteFlyweight();
            dic.Add(key, flyweight);
        }
        return (Flyweight)dic[key];
    }
}
/// <summary>
/// 客戶端調用
/// </summary>
public class App
{
    static void Main()
    {
        int extrinsicState = 26;
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight f1 = factory.GetFlyweight("oec2003", true);
        f1.Operation(++extrinsicState);
        Flyweight f2 = factory.GetFlyweight("oec2003", true);
        f2.Operation(++extrinsicState);
        Flyweight f3 = factory.GetFlyweight("oec2004", false);
        f3.Operation(++extrinsicState);
    }
}

 

Flyweight模式的幾個要點

面向對象很好地解決了抽象性的問題,但是作爲一個運行在機器中的程序實體,我們需要考慮對象的代價問題。Flyweight設計模式主要解決面向對象的代價問題,一般不觸及面向對象的抽象性問題。

Flyweight採用對象共享的做法來降低系統中對象的個數,從而降低細粒度對象給系統帶來的內存壓力。在具體實現方面,要注意對象狀態的處理。

對象的數量太大從而導致對象內存開銷加大——什麼樣的數量纔算大---這需要我們仔細的根據具體應用情況進行評估,而不能憑空臆斷。

發佈了115 篇原創文章 · 獲贊 1 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章