設計模式—工廠與抽象工廠 (Factory & Abstract Factory Design Pattern)

透過烤麵包的例子練習 C# 工廠設計模式

Wenchin
Wenchin Rolls Around

--

我的 mentor 丟給我 Refactoring Guru 的設計模式 (design pattern) 教學網站一陣子了,剛好連假回家看見我媽在揉麵團烤麵包,因此想借用烤麵包的例子,練習一下工廠方法 (Factory Method) 和抽象工廠方法 (Abstract Factory Method)。

GitHub Repo: 工廠方法 branch抽象工廠方法 branch

Photo by Nadya Spetnitskaya on Unsplash

來做麵包吧

今天我們想設計一間麵包店,先不要太複雜,就專做一種麵包:吐司。

首先要設計一個製造吐司的東西,可能是機器、團隊等等,這邊先當作是台機器,我們叫它 ToastMaker:

很簡單:讓這個吐司店有「生產吐司」的方法,回傳一個我們另外定義的 Toast 類別的物件。如果把 Toast 產出那段加工一下,可以針對 Toast 物件做些處理,讓它回傳不同的吐司(巧克力吐司、花生醬吐司等等),享受一下多型 (polymorphism) 的好處。

很快的我們會發現一個問題,今天假設我的麵包店想多樣化一些,覺得光賣吐司不夠,希望也推出另外一種麵包,例如蛋糕,那就不能用 Toast 這個類別的物件,也不能在 ToastMaker 做事。如果程式碼這樣寫要修改的時候會有點麻煩。

收整麵包種類

這時候我們可以收整各式麵包種類到 IBread 這個介面 (interface):

並且交由各種麵包(吐司、蛋糕……)自己去實作。這邊設計了吐司和蛋糕為例,蛋糕比吐司多了形狀這個屬性:

設計一個麵包工廠

創造了各種麵包種類後,現在我們把「製作麵包」功能拉出來,交給一個「工廠」負責。想賣的各式各樣麵包這個工廠都應該要做得出來,所以設定回傳型別為 IBread。

這個「工廠」我們透過一個抽象類別 (abstract class) 建立,命名為 Bakery,它要做的事就是產出麵包:

當我們界定這個方法後,各種麵包就可以各自用 override 實作 CreateBread 的方法。吐司交由 ToastFactory 繼承、蛋糕交由 CakeFactory 繼承:

工廠模式把麵包的創造裡會依據不同麵包而變動的部分 CreateBread 分離出去,交給工廠處理,因此方法會回傳不同的型別。

工廠會丟出一個 IBread,也就是麵包的一種。所以如果今天我想改變麵包的創造過程,那我可以在工廠處理,並在工廠的實作 (ToastFactory, CakeFactory…) 呼叫 CreateBread 方法時結果會不同。意即我只會在乎拿到的事不是 IBread 的子類別 (Toast, Cake…),而不會去管工廠怎麼創造出它的。

Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

而拿到 IBread 之後,也可以藉由 switch… case 語法操作型別轉換,做不同的反映。容我直接在 Program 操作我們的麵包店並讓 console 顯示:

Console output:

Let's make some bread!
Toast: Peanut butter toast
Toast: Chocolate toast
Cake: Cheesecake
* Cake's shape: square
Cake: Strawberry cake
* Cake's shape: round

工廠的使用會讓整個設計彈性很多,ToastFactory 和 CakeFactory 都繼承自 Bakery (也可以想成 BreadFactory),所以當我們需要吐司的時候,就從 ToastFactory 去 new 一個吐司;需要吐司的時候,就從 CakeFactory 去 new 一個蛋糕。

如果想要推出新的麵包,例如 Panini 好了,那我們也可以依樣畫葫蘆從 Bakery 實作一個 PaniniFactory,來生產帕尼尼。

Bakery 只是界定方法,但真正決定怎麼生產麵包的還是子類別們 (ToastFactory, CakeFactory…)。

工廠設計模式的結構

Source: Refactoring Guru — Design Patterns: Factory Method
  1. Product: 收整各種麵包物件的介面 IBread
  2. Concrete Product: 實作 IBread 的 Toast, Cake
  3. Creator: 宣告 CreateBread 方法的 Bakery
  4. Concrete Creator: 實作 Bakery 裡面 CreateBread 方法的 ToastFactory 和 CakeFactory,回傳型別為 Concrete Product (Toast, Cake…)

如果我想用麵包的原料分工呢?

今天如果我們麵包事業蒸蒸日上,想要開始專業分工、拆出不同的原料(麵粉、奶油、酵母粉等等)去做生產,那麼使用工廠方法就會異常複雜,因為可能因為產品定位或需求不同會有不同的原料:

  • 生產吐司用的麵粉工廠(高筋)
  • 生產吐司用的奶油工廠(橄欖油)
  • 生產蛋糕用的麵粉工廠(低筋)
  • 生產蛋糕用的奶油工廠(無鹽奶油)

如果每種麵包的蛋、鹽、糖、酵母粉等等也都要用不同的,那麼工廠方法就會讓系統非常複雜與難以維護。

此時我們會需要另一個維度的物件出現,去界定麵粉、奶油、酵母粉、蛋等等的實作,然後讓不同工廠使用不同原料物件,做出相異的麵包產品。

抽象工廠設計模式

這時抽象工廠 (Abstract Factory) 設計模式就派上用場了!

它是把工廠模式再抽象一層的設計模式。把本來生產麵包介面 IBread 拆成生產麵包的原料介面們,也就是不同種類麵粉、油、酵母粉等等的介面:IFlour, IOil, IPowder…,再到不同麵包的工廠 ToastFactory, CakeFactory… 當作該麵包要用的原料。

因為我們的假設是,製作麵包都要有麵粉、油、酵母粉…等等,只是類型和分量不同。例如蛋糕要用低筋麵粉、奶油,但吐司用的是高筋麵粉、橄欖油。而蛋糕可能起司蛋糕跟草莓蛋糕的麵粉和奶油份量佔比會不同。

收整麵包原料種類

現在我們把原料整理一下,交給不同的介面處理。這邊用麵粉跟油的介面舉例:

再實作麵粉跟油的類別:

建立抽象工廠、實作不同麵包工廠

有了不同的原料類別,我們就能抽象工廠 Bakery (也可以想成 BreadFactory),宣告 CreateFlour 跟 CreateOil 方法,讓實作的工廠去定義:

繼承這個抽象工廠後,我們就能開不同的麵包工廠:吐司工廠、蛋糕工廠……等等,去 override 剛剛宣告的方法:

抽象工廠能拆開不同族群的物件,讓原料成為另一個維度。

Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.

下圖是另一個例子:把家具和風格拆開成不同維度去抽象化。

Source: Refactoring Guru — Design Patterns: Abstract Factory Method

在 Client 丟進麵粉跟油的份量當作參數,就可以開始生產不同麵包了:

最後只要在 Program 呼叫 new Client().Main(); 即可。

Console output:

Let's make some bread!
Cheesecake
---
Flour name: cake flour
Flour amount: 300
Oil name: butter
Oil amount: 100
---
Strawberry cake
---
Flour name: cake flour
Flour amount: 400
Oil name: butter
Oil amount: 150
---
Chocolate toast
---
Flour name: bread flour
Flour amount: 200
Oil name: olive oil
Oil amount: 50
---
Peanut butter toast
---
Flour name: bread flour
Flour amount: 200
Oil name: olive oil
Oil amount: 80
---

抽象工廠設計模式的結構

Source: Refactoring Guru — Design Patterns: Abstract Factory Method
  1. Product: 收整各種麵包原料物件的介面 IFlour, IOil…
  2. Concrete Product: 實作 IFlour, IOil 的 CakeFlour, BreadFlour, Butter, OliveOil…
  3. Abstract Factory: 宣告 CreateBread 方法的 Bakery
  4. Concrete Factory: 實作 Bakery 裡面 CreateBread 方法的 FlourFactory 和 OilFactory…,回傳型別為 Concrete Product (CakeFlour, BreadFlour, Butter, OliveOil…)
  5. Client: 建立工廠並呼叫 CreateBread 方法,以得到 Product

Reference

--

--