工廠方法模式(Factory Method Pattern): 簡單工廠並不是一種設計模式

  1. 參考書籍: 《Design Patterns: Elements of Reusable Object-Oriented Software》
  2. Motivation for Simple Factory and Factory Method Pattern

工廠模式作爲提及頻率最爲頻繁的兩個模式之一(另一個是單例模式), 卻有着很多初學者都沒有搞清楚的誤區 。

工廠方法模式誤區一: 簡單工廠模式

首先, 23種設計模式中不存在簡單工廠這一模式 , 只有 工廠方法抽象工廠 兩種設計模式。 簡單工廠只是一個工廠形式的方法或類,將創建對象的操作集中了起來,充其量只能算是一個套路,而不是一種模式

public Pizza createPizza(String type){
     if(type.equals("cheese")){
        return new CheesePizza();
     }else if (type.equals("pepperoni")){
        return new PepperoniPizza();
     }
     ...
}

工廠方法模式誤區二: 抽象工廠模式與工廠方法模式的區別

很多中文的博文都沒有闡述清楚抽象工廠模式與工廠方法模式的區別, 致使很多初學者對抽象工廠模式和工廠方法模式的應用場景差別理解得很模糊。

爲了理解這兩種模式的區別,首先需要分開介紹兩種模式的適用場景

工廠方法模式(Factory Method)

  • 設計動機
    • 定義了一個父類(ClassA )之後,該父類( ClassA )需要實例化多個類(ClassE , ClassF, ClassG)中的其中一個 。 但是該父類希望將具體要實現E, F, G 中的哪一個類留給其子類(SubClassA)來決定。
    • 舉例: 假設現在要編寫一個支持屠宰家畜的框架, 家畜的類型包括牛羊等, 於是希望先定義一個屠宰場類 KillHouse, 該屠宰場會在調用KillAnimal(Animal animal) 方法時會接受運行時傳入的參數(Sheep、Chicken ) 。 現在假設, 宰殺家畜這個行爲需要一把刀(Knife), 但是宰殺不同類型的牲畜的刀不一樣,畢竟殺雞不能用牛刀嘛, 但是這些工具都提供了 chop() 的方法, 對於屠宰廠來說, 只需要調用chop() 方法即可, 但是屠宰場是沒辦法提前預知要實例化哪一種刀的, 這個決定必須留給其子類殺牛場和殺雞場來決定,這個時候就是應用工廠方法模式的時機
abstract public class KillHouse {

    protected abstract Knife createKnife();

    public void killAnimal(Animal animal)
    {
        // 有一些通用的準備步驟
        commonPrepareWork();
        // 有一些因動物類別而不同的準備步驟需要
        customPrepareWork();

        Knife knife = createKnife();
        knife.chop(animal); // 切割動物的操作
        // .... 有一些清理步驟
    }

    protected abstract void customPrepareWork();

    private void commonPrepareWork() {
        // 一些操作
    }

}
public class KillCowHouse extends KillHouse {
    @Override
    protected Knife createKnife() {
        return new CowKnife();
    }

    @Override
    protected void customPrepareWork() {
        //一些操作
    }

}
public class Knife {

    public void chop(Animal animal) {
    }
}
public class CowKnife extends Knife {
}

通過以上的說明以及例子,其實可以總結出工廠方法模式的幾個要素:

  • 工廠模式的核心是把一個實例化操作延遲到了子類。
    • 注意點一: 這裏並不是說簡單的把實例化操作集中到一個Factory 類就是工廠模式, 其中核心的參與者至少有四個:
      • 創建者的父類
      • 創建者的子類
      • 被創建者的父類
      • 被創建者的子類
    • 注意點二: 之所以要用工廠方法模式來把實例化操作留給子類完成的原因是父類沒有辦法預知應該實例化哪一種對象
    • 注意點三: 之所以存在創建者的父類和子類, 而不是簡簡單單隻有一個創建者累的原因是, 父類中的有一些操作, 希望被多個子類複用。
    • 注意點四: 在文章中舉的例子場景中, 實際上做了兩個假設:“殺雞不能用牛刀”和 “並沒有一把萬用刀”, 所以使用了創建對象的方法createKnife 被定義成了抽象的, 這樣強制了子類去實現該方法。 相反,此處如果不使用工廠模式, 採用如下寫法。
public class KillHouse {

    protected Knife = null;

    public void killAnimal(Animal animal)
    {
        // 有一些通用的準備步驟
        commonPrepareWork();
        // 有一些因動物類別而不同的準備步驟需要
        customPrepareWork();

        knife.chop(animal); // 切割動物的操作
        // .... 有一些清理步驟
    }

    protected abstract void customPrepareWork();

    private void commonPrepareWork() {
        // 一些操作
    }

}

此時 , KillHouse的子類在繼承時, 就必須要自己記得去對Knife 成員變量進行賦值, 如果忘記賦值, 就會導致空引用錯誤。 當然, 我們也可以認爲牛刀可以宰殺種家禽,將Knife 默認初始化爲 CowKnife 的實例, 但是這樣就會導致 “殺雞用了牛刀”等我們不一定希望看到的情形。

抽象工廠模式(Factory Method)

  • 設計動機
    • 提供了一個接口用於創建一個相互依賴的產品系列
    • 舉例: 假設有一個用於編寫用戶界面的 工具包/ 類庫, 該類庫支持編寫不同風格的操作界面(Windows 風格, Mac 風格, Metal 風格) 。不同風格的界面組件(按鈕, 滾動條, 菜單)的外觀和交互方式都會有一些或大或小的差別。
    • 爲了支持不同風格操作界面的切換, 應用程序在編寫界面時, 顯然不能把特定類型的風格組件硬編碼到應用層級的代碼中。
      • 此時並不能利用簡單工廠套路爲每個組件定義一個Facotry( ButtonFactory, ScrollBarFactory , MenuFactory ), 通過向getComponent(String style)傳入的參數來決定獲取哪一個類型的組件。 因爲如果這樣做, 應用程序在通過不同的Factory 獲取組件的時候, 都必須傳入類型參數, 因爲不同風格的組件肯定是不能被混用的,一旦在編寫程序的過程中, 同一個應用中,在不同的地方傳入了不同的風格參數, 除了會導致外觀的不一致以外, 由於同一風格的組件之間相互還會存在一些依賴,交互上也可能發生錯誤 。
    • 此時希望達到的效果是,我們只需要向一個面向Factory 編程, 可以從這個Factory 中獲取到不同類型的產品, getButton(), getScrollBar(), getMenu(), 而我們只需要在程序運行時, 在唯一的一處設置風格變量, 工具類會根據該變量, 在不同位置調用了獲取不同組件的方法, 返回風格一致的組件 。
public interface WidgetFactory {
    Button createButton();
    ScrollBar createScrollBar();
    Menu createMenu();
}
public class MacStyleFactory implements WidgetFactory{
    @Override
    public Button createButton() {
        return MacButton
    }

    @Override
    public ScrollBar createScrollBar() {
        return MacScrollBar;
    }

    @Override
    public Menu createMenu() {
        return MacMenu;
    }
}
  • 注意點一: 在以上的例子中, 抽象工廠模式的最大好處是, 通過一個接口, 和不同風格的工廠實現類, 保證了用戶在使用工具包編程時與其具體的組件風格完全解耦,獲取組件時, 只需要跟一個Factory交互,也只能夠跟一個風格的Factory交互(可以通過單例模式保證), 強制保證了一個應用中不會產生不一致風格的組件,致使發生顯示錯誤或交互錯誤。
  • 注意點二: 由於抽象工廠提供了的接口固定了產生的產品系列, 這相當於假設了不同風格的產品類型都是一致的, 假如說Windows 風格增加了一個在Mac風格中不存在的組件(Door) , 抽象工廠模式就失效了,因爲要繼承抽象工廠接口, 得實現其中的全部方法, 而MacFactory 肯定是沒有辦法實現createDoor 這個方法的。

工廠方法模式與抽象工廠模式區別總結

通過之前長篇幅的描述, 就可以發現:

  • 抽象工廠模式和工廠方法模式要解決的問題出發點完全不同。

    • 抽象工廠方法模式用於創造一系列相互依賴的產品, 且強制了這些產品只能被同時使用。
    • 工廠方法模式用於將實例化的操作延遲給子類完成。 所以使用了工廠方法的標誌是繼承關係, 是父類留了一個實例化對象的方法(可以是抽象方法, 也可以是已經實現的方法), 可供子類去實現或重寫, 而不是有一個工廠類,集中了創建對象的操作
    • 抽象工廠模式和工廠方法模式的聯繫在於, 抽象工廠方法模式通常是利用工廠方法模式實現的。 例如: WidgetFactory 中的createButton, createScrollBar等方法都留給了子類實現。
  • 如果要給抽象工廠工廠方法模式各給一個簡短易記的特徵,那應該是:

  • 抽象工廠–》 固定的產品系列
  • 工廠方法–》 創建操作的延遲
發佈了51 篇原創文章 · 獲贊 264 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章