1. 程式人生 > >不一樣的享元模式(設計模式四)

不一樣的享元模式(設計模式四)

前言

享元模式,表面意思是共享單元,屬於結構型設計模式。哦?good啊,如今共享文化高大上,共享肯定節約很多資源吧,肯定用的地方挺多吧,然而並不多,但是又是不可或缺的一種模式。
至於為什麼,請看正文部分,將會通過計算分析出為什麼用的地方不多,或者說有些地方為什麼不該用,同時得出為什麼屬於結構型,到底屬於結構型的哪一種。

開車觸發

介紹一下什麼是結構型:
結構型模式所描述的是如何將類和物件結合在一起來形成一個更大的結構,它描述兩種不同的事物:類和物件,根據這一點,可分為類結構型和物件結構型模式。類結構型模式關心類的組合,由多個類可以組合成一個更大的系統,在類結構型模式中一般只存在繼承關係和實現關係;物件結構型模式關心類與物件的組合,通過關聯關係使得在一個類中定義另一個類的例項物件,然後通過該物件呼叫其方法。根據“合成複用原則”,在系統中儘量使用關聯關係來替代繼承關係,因此大部分結構型模式都是物件結構型模式。

好了,先對結構型有一個小小的概念,然後出發吧。
有一個Font,字型類。

public class Font
{
    // unique
    private string key;
    //表示字型大小
    private int size;
    //表示字型顏色
    private string color;
    public Font(string key, int size,string color)
    {
        this.key = key;
        this.size = size;
        this.color = color;
    }
}

一個對字型進行渲染的靜態類。

public static class render
{
    public static void renderFont(char a,Font font) {
        //進行字型渲染
    }
}

main 中呼叫

static void Main(string[] args)
{
    Font fontx = new Font("xxx",12,"red");
    render.renderFont('a',fontx);
    Font fonty = new Font("yyy", 13, "red");
    render.renderFont('b',fonty);
    Font fontx2 = new Font("xxx", 12, "red");
    render.renderFont('c',fontx2);
}

如果仔細看下main中,會發現我渲染了兩個相同的:

Font fontx = new Font("xxx",12,"red");

我這是描述渲染過程,比如我們在渲染文章的時候是一個一個輸出,根本就不知道下一個要輸出什麼,它的字型型別是什麼。
上面描述的過程是當前字型型別為fontx,我寫下了a,然後使用字型fonty,去描述b,最後我有用字型fontx2(和fontx相同配置)去渲染c。
如此下去會有一個什麼問題呢?假設有10萬字需要渲染。
先查下Font 例項會佔用多少個byte。
在Font頭部加上StructLayout,使得它按照structLayout 對齊,否則無法取得其大小。

[StructLayout(LayoutKind.Sequential)]
public class Font

然後呼叫:

Font fontx = new Font("xxx",12,"red");
unsafe {
    var byteSize = Marshal.SizeOf(fontx);
    Console.WriteLine("查詢大小");
}

查詢結果為24位元組。
開始計算:
24byte * 110^6=2.410^7 /1024 /1024=2.410^7 /(1.04857610^6)約等於24m.
也就是假設我要渲染10w字我需要24m記憶體,可想而知,是需要優化的。
那就加上一個工廠類吧:

public class FontFactory
{
    private Hashtable flyweights = new Hashtable();

    public Font getFont(string key,int size,string color) {
        if (!flyweights.ContainsKey(key))
        {
            flyweights.Add(key,new Font(key,size,color));
        }
        return (Font)flyweights[key];
    }
}

呼叫:

static void Main(string[] args)
{
    FontFactory fontFactory = new FontFactory();
    Font fontx = fontFactory.getFont("xxx",12,"red");
    Font fonty = fontFactory.getFont("yyy", 13, "red");
}

通過一個font生成工廠,如果有的話就提取,沒有的話就生成,然後返回。
但是前面提及到了,這種模式使用的少。為什麼這麼說?
首先,FontFactory的機制需要消耗我們的cpu以及不可忽略的一部分記憶體。
如果產生的物件消耗只有幾k,那麼這種優化是幾乎沒有意義的。
第二呢,可以看到生產出來的Font要符合只讀,為何只讀呢?
如果需要修改的話,那麼也遇不到該問題,之所以不去修改,修改的消耗比較大。
所以場景並不是很多,但是伺服器端倒是有那麼多的地方使用。注:場景和使用的地方不是一個概念哈。
最後,看下物件結構型的概念。
物件結構型模式關心類與物件的組合,通過關聯關係使得在一個類中定義另一個類的例項物件,然後通過該物件呼叫其方法。根據“合成複用原則”,在系統中儘量使用關聯關係來替代繼承關係,因此大部分結構型模式都是物件結構型模式。
不言而喻,恰恰符合這種模式的概念。

uml圖

後續補上

總結

享元主要用於減少建立物件的數量,以減少記憶體佔用和提高效能。
場景:會產生特別多數量物件,消耗過多記憶體的情況