跳到主要內容

享元模式 (Flyweight Pattern)

        享元模式,又稱蠅量級模式 (Flyweight 是拳擊術語,指最輕量的級別,蠅量級),單看文字可能有點難理解,但簡單來說就是共享元件(資源)。試想一個情節:公司裡每個人都會有員工資訊,也都會有名片。假設名片是個類別時,對於每個員工,都需要 new 出名片的實體,而實際上名片裡也只是內容不同而已。當員工越多時,就需要 new 出越多名片物件,造成記憶體浪費,這時候就適合使用享元模式。以下為享元模式的正式定義及類別圖:

共享物件,用來儘可能減少記憶體使用量以及分享資訊給儘可能多的相似物件。
Use sharing to support large numbers of fine-grained objects efficiently.


  • Flyweight:所有具象 Flyweight 子類別的共同介面
  • ConcreteFlyweight:Flyweight 的具象子類別,就是要共享的元件類別。共享元件本身有內部的資料或狀態的話,都是在這裡維護。
  • FlyweightFactory:負責創建與管理共享元件 (如例子中的名片)。通常會確認共享元件是否已存在,存在就直接給 Client 用,否則就創建新的共享元件。
  • Client:取得共享元件並使用的角色,通常會結合外部資訊來使用共享元件 (如例子中的員工資訊)
// 名片是共享元件
// 提供一個介面讓子類別實作
public abstract class NameCard {

    // 名片要顯示的資訊
    protected String mId;
    protected String mDept;
    protected String mTel;

    public abstract void show(Employee e);
}

public class GeneralNameCard extends NameCard {

    @Override
    public void show(Employee e)
    {
        // 從外部資源取得資料
        mId = e.getId();
        mDept = e.getDept();
        mTel = e.getTel();

        // 顯示名片內容
        System.out.println(...)
    }
}

public class ManagerNameCard extends NameCard {

    // 管理階層的名片會多顯示職稱
    // 假設這是共享元件自己要管理的內部資源
    // 這邊只是範例, 把值寫死
    protected String mTitle = "Manager";

    @Override
    public void show(Employee e)
    {
        // 從外部資源取得資料
        mId = e.getId();
        mDept = e.getDept();
        mTel = e.getTel();

        // 顯示名片內容
        // 會多顯示職稱
        System.out.println(...)
    }
}

// 創建與管理共享元件的類別
public class NameCardFactory {

    // 範例用 Map 來儲存已創建的共享元件
    // 當然可以有不同的實作
    private static Map<Font, WeakReference<NameCard>> mNameCards =
        new WeakHashMap<Integer, WeakReference<NameCards>>();

    public static final int TYPE_GENERAL = 0;
    public static final int TYPE_MANAGER = 1;

    public static NameCard getNameCard(int employeeType)
    {
        if(employeeType == TYPE_GENERAL)
        {
            return getGeneralNameCard();
        }
        else if(employeeType == TYPE_MANAGER)
        {
            return getManagerNameCard();
        }
        else
        {
            // Error hanlding.
        }
    }

    // 確認共享元件是否存在
    // 不存在就創建並保留
    private static NameCard getGeneralNameCard()
    {
        if(mNameCards.get(TYPE_GENERAL) == null)
        {
            mNameCards.put(TYPE_GENERAL, new GeneralNameCard());
        }

        return mNameCards.get(TYPE_GENERAL).get();
    }

    private static NameCard getManagerNameCard()
    {
        if(mNameCards.get(TYPE_MANAGER) == null)
        {
            mNameCards.put(TYPE_MANAGER, new GeneralNameCard());
        }

        return mNameCards.get(TYPE_MANAGER).get();
    }
}

public class Main {

    public static void main(String[] args)
    {
        // 假設 Employee 相關類別已存在
        Employee generalEmployee1 = new GeneralEmployee(...);

        // 取得共享元件
        NameCard card1 = NameCardFactory.getGeneralNameCard();
        card1.display(generalEmployee1);

        Employee generalEmployee2 = new GeneralEmployee(...);

        // 取得共享元件
        NameCard card2 = NameCardFactory.getGeneralNameCard();
        // 這邊的結果會是 true
        System.out.println(card1 == card1);
    }
}
        在 Java 實際應用中,最簡單且容易了解的應該就是 String 了,當你用以下的方式取得 String:
String str1 = "Hello world";
String str2 = "Hello world";
System.out.println(str1 == str2);
        在Java中,會維護一個String Pool,對於一些可以共享的字串物件,會先在String Pool中查找是否存在相同的String內容(字元相同),如果有就直接傳回,而不是直接創造一個新的String物件,因此上面的結果會顯示 true。

        使用享元模式的最大優點,就是可以降低物件生成個數,以減少記憶體使用,且物件可被集中於一處來管理。假如你的系統中會生成很多物件,且物件本身的某些部份可以不用區分出差異(如範例中的名片不用特別區分是哪張名片),就可以使用此模式來分出共享元件與外部資源。而這個模式也是有缺點的,為了分出內外部資源,會造成程式架構較複雜,也因為大家都共用共享元件,無法有差異化,不同的行為

參考資料

        深入淺出設計模式(Head First Design Patterns)
        Chapter 10 享元模式(Flyweight Pattern)
        享元(Flyweight)模式
        Flyweight 模式

留言

這個網誌中的熱門文章

整理設計模式

        依據 GOF 的書,可以將經典的設計模式分為以下三類:生成、行為、結構。 生成模式 :牽涉到 將物件實體化 。這類模式都提供一個方法,將客戶從所需要實體化的物件中鬆綁出來。 獨體模式 (Singleton Pattern) 工廠方法模式 (Factory Method Pattern) 抽象工廠模式 (Abstract Factory Pattern) 建立者模式 (Builder Pattern) 原型模式 (Prototype Pattern) 結構模式 :讓你 合成類別或物件到大型的結構 。 裝飾者模式 (Decorator Pattern) 轉接器模式 (Adapter Pattern) 表象模式 (Facade Pattern) 合成模式 (Composite Pattern) 代理人模式 (Proxy Pattern) 橋接模式 (Bridge Pattern) 享元模式 (Flyweight Pattern) 行為模式 :模述 類別和物件如何互動 ,以及 各自的責任 。 策略模式 (Strategy Pattern) 觀察者模式 (Observer Pattern) 命令模式 (Command Pattern) 樣板方法模式 (Template Method Pattern) 反覆器模式 (Iterator Pattern) 狀態模式 (State Pattern) 責任鏈模式 (Chain of Responsibility Pattern) 解譯器模式 (Interpreter Pattern) 中介者模式 (Mediator Pattern) 備忘錄模式 (Memento Pattern) 訪問者模式 (Visitor Pattern)         有人可能會覺得裝飾者模式明明有替物件增加行為,為什麼不算是行為模式呢?我們可以從上面的結構模式得知, 結構模式用來描述類別或物件如何被合成,以建立新的結構或功能 。裝飾者模式允許你透過「 將某物件包裝進另一個物件的方式 」,將物件合成以提供新功能,因此焦點應該放在「 動態合成物件,以取得某功能 」,而不是物件之間的溝通。         設入淺出設計模式也有提到一些使用設計模式的

訪問者模式 (Visitor Pattern)

        假設你設計一個系統,其中會有一些相似類別,類別中都有某些方法內容相似,但還是需要判斷目前要做事的是哪個類別才能呼叫對應的適當類別。通常遇到這種情情,在 Java 中最直接的做法就是使用 instanceof 關鍵字來判斷,如以下的簡單範例: public interface CarComponent { public void printMessage(); } public class Wheel implements CarComponent { @Override public void printMessage() { System.out.println("This is a wheel"); } // 這是 Wheel 跟 Engine 不同的方法 public void doWheel() { System.out.println("Checking wheel..."); } } public class Engine implements CarComponent { @Override public void printMessage() { System.out.println("This is a engine"); } // 這是 Wheel 跟 Engine 不同的方法 public void doEngine() { System.out.println("Testing this engine..."); } } public class Car { private List mComponents; public Car() { mComponents = new ArrayList<carcomponent>(); } // 有些時候我們還是需要針對不同類別去做不同的事情 public void setComponent(CarCompon

裝飾者模式 (Decorator Pattern)

        假如你有一間飲料店, 目前只有賣幾種咖啡。因為生意很好, 因此想更換菜單…         以下是目前菜單的類別圖:         簡單說明此類別圖, cost() 是抽象的, 子類別要實作自己的 cost() 來告知飲料的價格。         買咖啡時, 也可要求要加料, 例如牛奶(Milk)、摩卡(Mocha,就是巧克力口味)。這樣的新類別要如何設計呢 ? 看起來是不能直接新增所需的子類別, 例如 EspressoWithMilk, EspressoWithMilkAndMocha, DarkRoastWithMilk, DarkRoastWithMilkAndMocha… 這樣加下去, 日後飲料跟配料越來越多時, 類別也就越多, 這實在不是個好設計。         換個方式設計呢, 在 Beverage 裡面加入所有的配料如何 ? 這樣好像也不太好, 未來要是配料有更動, Beverage 程式碼就要重寫, 而未來要是有新口味的飲料時, 有些配料就不太合理 ( 薑茶加摩卡 ? ), 更麻煩的是, 無法應付機車的客人 (例如要加 3 份牛奶)。這時候裝飾者模式就能上場啦。在介紹裝飾者模式前, 先說明其設計守則: 類別應該開放, 以便擴充 ; 應該關閉, 禁止修改。         我們的目標是允許類別容易擴充, 在不修改現有程式碼的情形就能搭配新的行為。這樣的設計具有彈性, 可以接受新功能以達到改變需求的目的。這看起來好像有點矛盾, 但是的確有一些技術可以在不直接修改程式碼的情形下進行擴充, 如裝飾者模式。         這時候應該有人會問: 那是不是以後我的專案架構設計都遵循這個守則就是好設計了 ? 答案是不太可能, 也沒這必要, 就算做得到, 也可能是浪費, 容易導致程式碼複雜且難以理解。只需小心選擇哪些部分未來會擴充, 這些部份遵循這個設計守則即可。         接下來正式介紹裝飾者模式的定義:  裝飾者模式動態地將責任加諸於物件上。若要擴充功能,裝飾者模提供了比繼承更有彈性的選擇 Attach additional responsibilities to an object dynamically. Decorators provide a flexible alt