天天看點

設計模式(11) 享元模式

基于面向對象思想設計的應用程式有時遇到需要場景大量相同或顯示對象執行個體的場景,這些數量龐大的執行個體很可能會消耗很多系統資源,最直接的就是記憶體了。比如要一款圍棋遊戲,如果每次落子都建立一個對象,将會占用大量記憶體,而實際上棋子隻有黑白兩色,不同的隻是落子位置而已。另外,大量的主動型對象還會占用很多CPU和顯示卡的計算資源,舉個極端的例子,某個遊戲的沙漠場景,為了使遊戲具有豐富的視覺效果,要求每一粒沙子都要随着光線而有不同的呈現效果不同,這時候直接new當然也是不現實的。

享元模式

享元模式提供了一種針對這類場景的解決方案。它通過共享已經存在的對象來大幅度減少需要建立的對象數量、避免大量相似對象的開銷,進而提高系統資源的使用率,支援大量細粒度對象的複用。

UML類圖:

代碼實作

public abstract class Flyweight
{
    //内部狀态
    public string Instrinsic { get; set; }
    //外部狀态
    protected string Extrinsic { get; set; }

    public Flyweight(string extrinsic)
    {
        this.Extrinsic = extrinsic;
    }

    //定義業務操作
    public abstract void Operate(int id);
}

public class ConcreteFlyweight : Flyweight
{
    //接受外部狀态
    public ConcreteFlyweight(String extrinsic) : base(extrinsic)
    {
    }

    //根據外部狀态進行邏輯處理
    public override void Operate(int id)
    {
        Console.WriteLine("Flyweight:" + id);
    }
}

public class UnsharedConcreteFlyweight : Flyweight
{

    public UnsharedConcreteFlyweight(String extrinsic) : base(extrinsic)
    {
    }

    public override void Operate(int id)
    {
        Console.WriteLine("不共享的Flyweight:" + id);
    }
}

public class FlyweightFactory
{
    //定義一個池容器
    private static Dictionary<String, Flyweight> pool = new Dictionary<String, Flyweight>();

    //享元工廠
    public static Flyweight GetFlyweight(string extrinsic)
    {
        Flyweight flyweight = null;
        if (pool.ContainsKey(extrinsic))
        {
            flyweight = pool[extrinsic];
            Console.Write($"已有{extrinsic} ");
        }
        else
        {
            flyweight = new ConcreteFlyweight(extrinsic);
            pool.Add(extrinsic, flyweight);
            Console.Write($"建立{extrinsic} ");
        }
        return flyweight;
    }
}
           

享元模式的核心是用一個池容器來緩存需要共享的對象,C#可以用Dictionary來實作。

内部狀态與外部狀态

由于這些數量較大的細粒度對象有着相近的性質,為了能共享這些對象,需要将這些對象的資訊分為兩個部分:内部狀态和外部狀态。

  • 内部狀态指對象共享出來的資訊,存儲在享元對象内部并且不會随環境的改變而改變;
  • 外部狀态指會随環境改變而改變的、不可共享的狀态。

    比如前面圍棋的例子中,棋子的黑白兩色就可作為内部狀态,落子位置則是外部狀态,将内部狀态(黑、白兩色)作為對象間的本質差別,隻需要兩個對象就可以了,然後配合外部狀态(落子位置)的變化,就可以表示全部的棋子。

圍棋例子的誤導

使用圍棋這個例子會容易讓人産生一個困惑:既然實際上隻有兩個對象(黑、白),那麼是如何讓同一個對象即出現在位置A,又出現在位置B的呢?難不成像薛定谔的貓那樣,可以有多種狀态?

實際上這裡所謂的共享對象,共享的應該是對象的行為。在圍棋遊戲中可以了解為,在畫布上繪制的棋子圖案。每個棋子對象都有個繪制棋子圖案的行為,通過設定不同的外部狀态(落子位置),就棋子的繪圖行為就會在畫布上不同的位置繪制圖案。

享元模式的适用場景

  • 系統中有大量對象。
  • 這些對象消耗大量記憶體。
  • 這些對象的狀态大部分可以外部化。
  • 這些對象可以按照内部狀态分為很多組,當把外部對象從對象中剔除出來時,每一組對象都可以用一個對象來代替。
  • 系統不依賴于這些對象的身份,這些對象是不可分辨的。

享元模式的優缺點

優點

  • 大大減少對象的建立,降低系統的資源占用,提高效率。
  • 由于抽離出了外部狀态和内部狀态,外部狀态相對獨立,不會影響到内部狀态,是以享元模式使得享元對象能夠在不同的環境被共享。

    缺點

  • 增加了系統的複雜度,需要分離出外部狀态和内部狀态,而且外部狀态具有固有化的性質,不應該随着内部狀态的變化而變化,否則會造成系統的混亂。
  • 為了使對象可以共享,享元模式需要将享元對象的狀态外部化,而讀取外部狀态使得運作時間變長。

參考書籍:

王翔著 《設計模式——基于C#的工程化實作及擴充》

繼續閱讀