跳到主要內容

原型模式 (Prototype Pattern)

        原型模式,光看字面上的意思很難了解這在幹麻,我們直接先來看正式定義及類別圖:

(中文好難翻譯,我不會…)
Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype.


        從定義上我們大概可以知道,這個模式的用途是讓我們可以透過複製的方式產生一個新物件。可是產生物件幹麻要這麼麻煩,不是直接 new 出一個新物件就好了嗎? 還是一句老話:有時建立物件的代價很高。假如你所需要建立的物件,其所需的資料是放在遠端資料庫的,這樣當每建立一個物件,就要讀取資料庫一次,這其實是很花成本的。

        從類別圖我們可以看到,有一個公開的介面 Prototype,其子類別都需要實作 clone。而 client 這邊在取得物件時,不是透過 new 的方式,而是使用 clone() 來取得所需的物件。因此我們可以知道,此模式的重點在於 client 的程式碼可以在不知道特定類別為何的情形下可以生成新的物件

        接下來來看簡單的範例程式碼吧。在 Java 中,每個類別都繼承了 Object 這個類別,表示每個類別都含有 clone() 這個方法。但是要讓 clone() 能動,類別還必須實作 java.lang.Cloneable 這個標籤介面(Tag Interface,Marker Interface)。除了這點之外,還要注意你的 clone() 是想要淺層複製 (shallow copy) 還是深層複製 (deep copy)。

// 在 Java, 要實作 Cloneable
// 才能 override Object 的 clone() 方法
public class SomeObj implements Cloneable {

    // 某個 primitive type 成員,
    // shallow copy 可以複製值
    private int mNum;

    // 某個類別物件,
    // shallow copy 只會複製 reference,
    private Obj mObj;

    @Override
    public SomeObj clone()
    {
        SomeObj obj = null;
        try
        {
            // 先做原物件的複製
            obj = (SomeObj)super.clone();

            // 再做物件成員的複製,
            // 假設 Obj 類別也有做好 clone()
            obj.setObj(mObj.clone());
        }
        catch(CloneNotSupportedException e)
        {
            e.printStackTrace();
        }
    }

    public void setObj(Obj obj)
    {
        mObj = obj;
    }
}
        有人可能會有疑問,原型模式跟抽象工廠模式都是生成物件,這兩個有什麼差別呢?其實很多時候生成物件的設計模式沒有很大的區別,就 client 的角度都是透過某個方法就能取得物件,但對於物件類別的架構而言,使用原型模式的話,子類別不用繼承父類別來達到其他功能 (Subclassing),但需要有初始化的行為 (總要先產生一個物件,之後才能用 clone 的);而抽象工廠模式通常是利用工廠方法來取得物件,而工廠方法需要 subclassing。

參考資料:

        深入淺出設計模式
        Prototype 模式
        Explanation of Deep and Shallow Copying

留言

這個網誌中的熱門文章

訪問者模式 (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 份牛奶)。這時候裝飾者模式就能上場啦。在介紹裝飾者模式前, 先說明其設計守則: 類別應該開放, 以便擴充 ; 應該關閉, 禁止修改。         我們的目標是允許類別容易擴充, 在不修改現有程式碼的情形就能搭配新的行為。這樣的設計具有彈性, 可以接受新功能以達到改變需求的目的。這看起來好像有點矛盾, 但是的確有一些技術可以在不直接修改程式碼的情形下進行擴充, 如裝飾者模式。         這時候應該有人會問: 那是不是以後我的專案架構設計都遵循這個守則就是好設計了 ? 答案是不太可能, 也沒這必要, 就算做得到, 也可能是浪費, 容易導致程式碼複雜且難以理解。只需小心選擇哪些部分未來會擴充, 這些部份遵循這個設計守則即可。         接下來...

狀態模式 (State Pattern)

        如果今天你要設計一台如下圖的糖果機,你會怎麼設計呢?         有上過資訊相關課程的人,應該不難從上圖聯想到 狀態圖 ,上圖中每個圓圈都是一個狀態,而每個箭頭就代表狀態的轉換。有了這個概念後,把它轉成程式就不難了: public class CandyMachine { // 以下四個值表示糖果機會用到的狀態 final static int SOLD_OUT = 0; final static int NO_COIN = 1; final static int HAS_COIN = 2; final static int SOLD = 3; // 需要有一個變數來記錄目前的狀態 // 初始設為賣完, 因為一開始機器裡沒糖果 int mState = SOLD_OUT; // 也要有另一個變數記錄目前剩多少顆糖果 int mCount = 0; public CandyMachine(int count) { mCount = count; // 機器內有糖果的話就跳到沒投錢的狀態, // 表示糖果機在等人投錢 if(mCount > 0) { mState = NO_COIN; } } // 當投錢時會執行這個方法 public void insertCoin() { if(mState == HAS_COIN) { System.out.println("You can't insert another coin"); } else if(mState == NO_COIN) { mState == HAS_COIN; System.out.println("You inserted a coin"); } ...