跳到主要內容

工廠模式 - 工廠方法模式 (Factory Method Pattern)

        延續前一篇簡單工廠的 Pizza 店,當你想要開放加盟時,加盟店可能因為不同地區要有不同的口味,如要用紐約風味或芝加哥風味時,要怎麼做呢?假如用 SimplePizzaFactory 的方式,寫出不同的工廠,如 NYPizzaFactory,ChicagoPizzaFactory,這樣各地加盟店都能有適合的工廠可以使用,但是其他部份可能使用加盟店自己的流程,如切片方式,裝盒方式不同。因為工廠只管產出 Pizza,並不負責後續的處理。當你想要對 Pizza 有多一些品質控管時,應該如何做呢?

        其實要改變的程式不多,只要把 createPizza() 放回 PizzaStore 中,並把這個方法設定為抽象方法,然後為每個不同地區的加盟店,建立一個 PizzaStore 次類別:


// 記得要設定為抽象類別
public abstract class PizzaStore {

    // 為了處理訂單一致,要讓子類別不能修改此方法
    final public Pizza orderPizza(String type)
    {
        Pizza pizza;

        // createPizza() 從工廠移回 PizzaStroe
        pizza = createPizza(String type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    // 把工廠要做的事改成這個抽象工廠方法
    abstract Pizza createPizza(String type);
}
        orderPizza() 是被宣告在抽象的 PizzaStore 內,因此也不知道次類別如何實際製作 Pizza。而當 orderPizza() 呼叫 createPizza() 時,某個具象的次類別 PizzaStore 會負責建立 Pizza。因此我們的紐約風 Pizza 店可以這樣寫:

// NYPizzaStore 也擁有 orderPizza() 及其他方法
public class NYPizzaStore extends PizzaStore {

    // 只要是 PizzaStore 的次類別就一定要實作此方法
    Pizza createPizza(String item)
    {
        if(item.equals("cheese"))
        {
            return new NYCheesePizza();
        }
        else if(item.equals("beef"))
        {
            return new NYBeefPizza();
        }
        else
        {
            return null;
        }
    }
}

        從上面兩段程式碼可以看到,實體化 Pizza 的工作被移到了一個方法,而這個方法就如同是前一篇的工廠。這樣的作法可以將客戶程式碼 (也就是父類別中的方法,如 orderPizza ) 和實際建立產品 (如 Pizza ) 的程式碼分隔開來。在此我們都省略了 Pizza 跟所有 Pizza 次類別的程式碼,我們一樣可以建立抽象的 Pizza 類別,並定義一些方法,如 bake(),cut()。而 Pizza 次類別需要的話也可以覆寫這些已定義好的方法。

        以下就是工廠方法模式的正式定義:

工廠方法模式定義了一個建立物件的介面,但由次類別決定要實體化的類別為何者。工廠方法讓類別把實體化的動作,交由次類別進行
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

        工廠方法模式可以封裝「具象型態的實體化」。用類別圖來看,抽象的 Creator 提供一個方法能建立物件,這樣的方法就叫做「工廠方法」。在 Creator 中的其他方法都能使用工廠方法所製造出來的產品,但只有次類別真正實作這個方法,產生出物件。

        另外要注意的是,定義中的「決定」並不是指此模式允許次類別本身在執行期做決定,而是指:在寫 Creator 類別時,不需要知道實際建立的產品是什麼。選擇使用哪個次類別,自然就決定要建立的產品為何。

        最後來討論物件的相依性。以最一開始的 PizzaStore 來看,createPizza() 裡面包含了要建立所有 pizza 的 if 判斷式,未來若是某個 pizza 次類別修改了,或是要新增新的 Pizza,PizzaStore 也要跟著修改,這樣的關係就像是 PizzaStore 「依賴」 Pizza 的實作方式。而以下就是說明減少依賴具象類別的設計準則:顛覆依賴守則 (Dependency Inversion Principle)

依賴抽象類別,不要依賴具象類別

        這守則說明了不要讓高階元件依賴低階元件。高階元件指的是由其他低階元件定義其類別的行為,如 PizzaStore prepare,bake,cut Pizza 物件; 而 Pizza 本身就是屬於低階物件。此守則也說明不管高階低階元件,都應該相依於抽象類別。以此篇文章的 PizzaStore 加上工廠方法模式就能發現,PizzaStore 依賴 Pizza 這個「抽象類別」,而所有的 Pizza 次類別也依賴了 Pizza 抽象類別。

        最後提供幾個設計方針,能避免 OO 設計中,違反覆相依守則:
  1. 變數不可持有具象類別的參考。可以改用工廠避開直接 new 的做法
  2. 不要讓類別繼承自具象類別。繼承具象類別,就會依賴具象類別。請繼承自一個介面(或抽象類別)。
  3. 不要讓次類別中的方法 override 超類別中的方法。如果要 override 超類別的方法,那超類別就不是一個適合被繼承的抽象。超類別中的方法,應該由所有次類別共享
        這些方針只是盡量去達到,不是隨時要遵守。例如有一個不會改變的類別,這時直接 new 也沒關係。而要是類別在未來可能改變時,可以使用一些技巧 (例如工廠) 將這些改變封裝起來。

參考資料:

        深入淺出設計模式(Head First Design Patterns)

留言

這個網誌中的熱門文章

整理設計模式

        依據 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