在軟體開發的領域中,物件的建立是一個基礎且頻繁的操作。然而,隨著專案規模的擴大與需求的變更,如何優雅地管理物件的建立過程,便成為一門重要的學問。本文將透過一個生活化的早餐店案例,帶您一步步了解「簡單工廠模式」(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 種設計模式之一,但它是一種廣泛被使用的編程習慣。透過將物件的建立過程封裝在一個工廠類別中,我們達成了以下優點:
責任分離:將物件的建立與使用分開 ,降低了客戶端與具體產品類別之間的耦合 。
單一權責:工廠類別專門負責建立物件,符合單一權責原則。
易於擴充:當需要新增產品時,僅需修改工廠類別,客戶端程式碼無需變動,這呼應了「開放擴充、關閉修改」的原則 。
從皓田早餐店的例子中,我們可以看到,一個好的設計模式能夠像一個好的制度,讓早餐店在面對產品線擴充時,依然能有條不紊地運作。