</> 技術筆記Tech Notes

初探設計模式:從早餐店案例學習簡單工廠模式

在軟體開發的領域中,物件的建立是一個基礎且頻繁的操作。然而,隨著專案規模的擴大與需求的變更,如何優雅地管理物件的建立過程,便成為一門重要的學問。本文將透過一個生活化的早餐店案例,帶您一步步了解「簡單工廠模式」(Simple Factory Pattern)如何解決物件建立的問題,並提升程式碼的彈性與可維護性。

緣起:皓田早餐店的開張

故事從「皓田早餐店」的開張說起 。起初,店裡只販售「火腿蛋三明治」一種產品 。對於程式來說,製作一份三明治的流程包含了烤麵包、抹醬料、煎蛋、加料和包裝等步驟 。

classDiagram
  class 火腿蛋三明治 {
    +烤麵包()
    +抹醬料()
    +煎蛋()
    +加料()
    +包裝()
  }

當客人點餐時,我們直接建立一個 火腿蛋三明治 的物件,並依序呼叫其方法來完成製作 。

// Main.java
public class Main {
    public static void main(String[] args) {
        火腿蛋三明治 三明治 = new 火腿蛋三明治(); // 直接建立物件 [cite: 29]
        三明治.烤麵包(); [cite: 30]
        三明治.煎蛋(); [cite: 31]
        三明治.抹醬料(); [cite: 32]
        三明治.加料(); [cite: 33]
        三明治.包裝(); [cite: 34]
    }
}

新挑戰:新增「鮪魚蛋三明治」

幾天後,早餐店生意興隆,決定新增「鮪魚蛋三明治」這個品項,讓顧客有更多的選擇 。這時,我們發現不論是火腿蛋三明治還是鮪魚蛋三明治,其製作過程是相同的 。根據物件導向程式設計(OOP)的原則,我們可以定義一個「三明治」的介面(Interface),將這些共通的製作步驟標準化 。

classDiagram
  class 三明治 {
    <<Interface>>
    +烤麵包()
    +抹醬料()
    +煎蛋()
    +加料()
    +包裝()
  }
  class 火腿蛋三明治 {
    +烤麵包()
    +抹醬料()
    +煎蛋()
    +加料()
    +包裝()
  }
  class 鮪魚蛋三明治 {
    +烤麵包()
    +抹醬料()
    +煎蛋()
    +加料()
    +包裝()
  }
  三明治 <|-- 火腿蛋三明治
  三明治 <|-- 鮪魚蛋三明治

如此一來,客戶端(Main 函式)的程式碼會變成 :

// Main.java
String 客人要的三明治 = args[0]; [cite: 54]
三明治 三明治 = null; [cite: 55]

if (客人要的三明治.equals("火腿蛋三明治")) { [cite: 56]
    三明治 = new 火腿蛋三明治(); [cite: 57]
} else if (客人要的三明治.equals("鮪魚蛋三明治")) { [cite: 58]
    三明治 = new 鮪魚蛋三明治(); [cite: 59]
} else {
    System.out.println("很抱歉,我們沒賣" + 客人要的三明治); [cite: 61]
    System.exit(1); [cite: 62]
};

三明治.烤麵包(); [cite: 64]
三明治.煎蛋(); [cite: 65]
// ... 其他製作步驟 [cite: 66, 67, 68]

雖然使用介面讓製作流程變得統一,但問題也隨之而來。每當要新增一種三明治(例如:培根蛋三明治),我們就必須修改

Main 函式中的 if-else 判斷式 。這違反了物件導向設計中重要的「開放封閉原則」—— 對於擴充是開放的,但對於修改是封閉的 。

解決方案:導入「三明治工廠」

為了解決這個問題,「簡單工廠模式」應運而生。其核心精神在於分離物件的使用與建構

我們建立一個專門負責生產三明治的 三明治工廠Simple Factory),將建立物件的邏輯封裝起來 。這個工廠的角色是利用分支判斷(如 if-else)來決定要建立哪一種產品的實體 。

classDiagram
  class 三明治工廠 {
    +製作三明治(String 種類) 三明治
  }
  class 三明治 {
    <<Interface>>
    +烤麵包()
    +抹醬料()
    +煎蛋()
    +加料()
    +包裝()
  }
  class 火腿蛋三明治 {
    +烤麵包()
    +抹醬料()
    +煎蛋()
    +加料()
    +包裝()
  }
  class 鮪魚蛋三明治 {
    +烤麵包()
    +抹醬料()
    +煎蛋()
    +加料()
    +包裝()
  }

  三明治工廠 ..> 三明治 : creates
  三明治 <|-- 火腿蛋三明治
  三明治 <|-- 鮪魚蛋三明治

透過工廠,原本在 Main 函式中複雜的物件建立邏輯被封裝起來了 。

// 三明治工廠.java [cite: 74]
public class 三明治工廠 {
    public 三明治 製作三明治(String 種類) { [cite: 75]
        三明治 產品 = null; [cite: 76]

        if (種類.equals("火腿蛋三明治")) { [cite: 77]
            產品 = new 火腿蛋三明治(); [cite: 78]
        } else if (種類.equals("鮪魚蛋三明治")) { [cite: 79]
            產品 = new 鮪魚蛋三明治(); [cite: 80]
        };

        return 產品; [cite: 82]
    }
}

現在,Main 函式只需要告訴工廠它需要什麼種類的三明治,而不需要知道三明治是如何被建立的。

// Main.java [cite: 85]
public class Main {
    public static void main(String[] args) { [cite: 86]
        三明治工廠 工廠 = new 三明治工廠(); [cite: 87]
        String 客人要的三明治 = args[0]; [cite: 88]
        
        // 將建立三明治的責任交給工廠 [cite: 89]
        三明治 三明治 = 工廠.製作三明治(客人要的三明治);
        
        三明治.烤麵包(); [cite: 90]
        三明治.煎蛋(); [cite: 91]
        三明治.抹醬料(); [cite: 92]
        三明治.加料(); [cite: 93]
        三明治.包裝(); [cite: 94]
    }
}

如此一來,客戶端程式碼中不再有判斷邏輯,去除了與具體產品的依賴 ,完全交由工廠來處理。未來若需要增加新的三明治品項,只需修改工廠內部的邏輯,而不需要更動到客戶端的程式碼,這使得系統更有彈性且易於維護。

總結

簡單工廠模式雖然不屬於 GoF(Gang of Four)所定義的 23 種設計模式之一,但它是一種廣泛被使用的編程習慣。透過將物件的建立過程封裝在一個工廠類別中,我們達成了以下優點:

  • 責任分離:將物件的建立與使用分開 ,降低了客戶端與具體產品類別之間的耦合 。

  • 單一權責:工廠類別專門負責建立物件,符合單一權責原則。

  • 易於擴充:當需要新增產品時,僅需修改工廠類別,客戶端程式碼無需變動,這呼應了「開放擴充、關閉修改」的原則 。

從皓田早餐店的例子中,我們可以看到,一個好的設計模式能夠像一個好的制度,讓早餐店在面對產品線擴充時,依然能有條不紊地運作。