工廠模式提供了可創建(new)各種不同類別(class)的實例(instance)
工廠模式主要又涵蓋了 (1) 簡單工廠模式 Simple Factory Pattern (2) 工廠方法模式 (3) 抽象方法模式
簡單工廠方法是最初階的用法,會違反OOP的Open-close principle(OCP)
然而工廠方法和抽象方法則比較能讓模組間耦合度降低。
以下會依序做介紹
簡單工廠模式 Simple Factory Method
簡單工廠模式僅提供一個工廠類別,接收參數(決定生產哪種產品),來產生出對應的產品物件(Object)
以生產Pizza為例,假設我們有個披薩簡單工廠(SimplePizzaFactory)可以生產兩種不同口味的Pizza(Cheese和Potato):
SimplePizzaFactory.java
1 2 3 4 5 6 7 8 9 10 11 12
| public class SimplePizzaFactory { public Pizza createPizza(String type) { Pizza pizza = null;
if(type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("potato")) { pizza = new PotatoPizza(); } return pizza; } }
|
工廠會回傳抽象的Pizza類別
Pizza.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public abstract class Pizza { String name; String ingridient; void prepare() { System.out.println("Preparing" + name); System.out.println("Ingredient" + ingridient); }
void bake() { System.out.println("Bake for 20 mins"); }
String getName() { return name; } }
|
以及實作了兩個口味的披薩 CheesePizza和PotatoPizza
CheesePizza.java
1 2 3 4 5
| public class CheesePizza extends Pizza { String name = "CheesePizza"; String ingridient = "Cheese"; }
|
PotatoPizza.java
1 2 3 4
| public class PotatoPizza extends Pizza { String name = "PotatoPizza"; String ingridient = "Potato"; }
|
Pizza都定義完畢後,定義SimplePizzaFactory.java
SimplePizzaFactory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class SimplePizzaFactory { public void orderPizza(String type) { Pizza pizza = null; pizza = createPizza(type); pizza.prepare(); pizza.bake(); }
public Pizza createPizza(String type) { Pizza pizza = null; if(type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("potato")) { pizza = new PotatoPizza(); } return pizza; } }
|
在Main.java透過SimplePizzaFactory製作兩個Pizza:
Main.java
1 2 3 4 5 6 7 8 9
| public class Main { public static void main(String[] args) { SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory(); simplePizzaFactory.orderPizza("cheese"); simplePizzaFactory.orderPizza("potato"); } }
|
執行Main.java
1 2 3 4 5 6
| Preparing: CheesePizza Ingredient: Cheese Bake for 20 mins Preparing: PotatoPizza Ingredient: Potato Bake for 20 mins
|
但此若之後要擴充Pizza的種類,例如要多一個叫做OnionPizza,勢必得修改SimplePizzaFacotory.java
SimplePizzaFactory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class SimplePizzaFactory { public void orderPizza(String type) { Pizza pizza = null; pizza = createPizza(type); pizza.prepare(); pizza.bake(); }
public Pizza createPizza(String type) { Pizza pizza = null; if(type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("potato")) { pizza = new PotatoPizza(); } else if (type.equals("onion")) { pizza = new OnionPizza(); } return pizza; } }
|
:::danger
上述做法破壞了OOD的OCP(Open-Close Principle)的原則,亦即進行擴充時不修改到SimplePizzaFactory的程式碼。
:::
工廠方法模式
為了改善簡單工廠模式,工廠方法模式透過"抽象方法"的模式,讓之後擴充的工廠實作該"抽象方法",以進行其他類別的擴充
舉例:有個PizzaFactory負責定義了Pizza工廠該做的一件事(抽象方法)
叫做 createPizza();
PizzaFactory.java
1 2 3 4 5 6 7 8 9 10
| public abstract class PizzaFactory { public void orderPizza(String type) { Pizza pizza = null; pizza = createPizza(type); pizza.prepare(); pizza.bake(); }
abstract Pizza createPizza(String type); }
|
原本我們已經有兩種口味(Cheese和Potato) Pizza,叫做原味披薩(OriginalPizzaFactory), 使OringinalPizzaFactory去實作PizzaFactory的createPizza()方法
OringinalPizzaFactory.java
1 2 3 4 5 6 7 8 9 10 11 12
| public class OriginalPizzaFactory extends PizzaFactory {
public Pizza createPizza(String type) { Pizza pizza = null; if(type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("potato")) { pizza = new PotatoPizza(); } return pizza; } }
|
若要新增洋蔥口味的Pizza的話,那就直接在定義一個額外的工廠叫做,特別披薩工廠 (SpecialPizzaFactory)進行擴充,如此一來就不用更動到到原本原味披薩工廠(OriginalPizzaFactory)的程式碼
SpecialPizzaFactory.java
1 2 3 4 5 6 7 8 9
| public class SpecialPizzaFactory extends PizzaFactory { public Pizza createPizza(String type) { Pizza pizza = null; if(type.equals("onion")) { pizza = new OnionPizza(); } return pizza; } }
|
記得多新增一個洋蔥披薩的類別
OnionPizza.java
1 2 3 4 5 6
| public class OnionPizza extends Pizza { public OnionPizza() { name = "OnionPizza"; ingridient = "Onion"; } }
|
在Main.java中呼叫原味和特別口味的披薩
Main.java
1 2 3 4 5 6 7 8 9 10 11
| public class Main { public static void main(String[] args) { PizzaFactory originalPizzaFactory = new OriginalPizzaFactory(); originalPizzaFactory.orderPizza("cheese"); originalPizzaFactory.orderPizza("potato");
PizzaFactory specialPizzaFactory = new SpecialPizzaFactory(); specialPizzaFactory.orderPizza("onion"); } }
|
透過下面的圖可以看到,透過定義一個抽象方法的工廠,若要擴充產品(在這裡我們用的是Pizza)類別的話,透過實作該抽象方法工廠,來進行擴充。
由於我們亦把Pizza類別給獨立出來成為一個抽象類別,若要新增其他口味的Pizza也可以達到不違反OCP精神的擴充,如下圖所示:
抽象工廠
抽象工廠顧名思義就是定義一個抽象介面工廠,欲擴充的話就實作該抽象介面工廠
以披薩原料工廠(PizzaIngredientFactory)為例,有醬料和蔬菜兩種原料:
方面給之後我們要製作的中式和美式披薩的原料實作,做擴展:
PizzaIngredientFactory.java
1 2 3 4
| public interface PizzaIngredientFactory { public Sauce createSauce(); public Vegetable createVegetable(); }
|
抽象工廠(原料工廠)在透過工廠方法來製作原料產品
我們再分別產生美式版本(AmericaIngredientFactory)和中式版本(ChinesePizzaFactory)的披薩原料工廠,可以看到使用了createSauce和createVegetable,透過工廠方法來產生原料產品
AmericaIngredientFactory.java
1 2 3 4 5 6 7 8
| public class AmericaIngredientFactory implements PizzaIngredientFactory { public Sauce createSauce() { return new TomatoSauce(); } public Vegetable createVegetable() { return new Potato(); } }
|
ChineseIngredientFactory.java
1 2 3 4 5 6 7 8
| public class ChineseIngredientFactory implements PizzaIngredientFactory { public Sauce createSauce() { return new SoySauce(); } public Vegetable createVegetable() { return new Onion(); } }
|
接著定義剛剛原料工廠有使用到的原料類別們(Sauce: SoySauce與PotatoSauce; Vegetable: Onion和Potato,該原料亦在實作Pizza類別的具體類別亦會用到
:::info
Pizza.java
1 2 3 4 5 6 7 8 9
| public abstract class Pizza { String name; Sauce sauce; Vegetable vegetable; ...
}
|
:::
定義醬料Sauce原料們:
Sauce.java
1 2 3 4 5
| public abstract class Sauce { String name;
public abstract String getSauce(); }
|
SoySauce.java
1 2 3 4 5 6 7 8 9 10
| public class SoySauce extends Sauce { public SoySauce() { name = "soy sauce"; }
public String getSauce() { System.out.println("The sauce is :" + name); return this.name; } }
|
TomatoSauce.java
1 2 3 4 5 6 7 8 9 10 11
| public class TomatoSauce extends Sauce { public TomatoSauce() { name = "tomato sauce"; }
public String getSauce() { System.out.println("The sauce is :" + name); return this.name; } }
|
接下來換定義Vegetable原料們
Vegetable.java
1 2 3 4 5
| public abstract class Vegetable { String name;
public abstract String getVegetable(); }
|
Onion.java
1 2 3 4 5 6 7 8 9
| public class Onion extends Vegetable { public Onion() { name = "onion"; } public String getVegetable() { System.out.println("The vegetable is :" + name); return this.name; } }
|
Potato.java
1 2 3 4 5 6 7 8 9 10 11
| public class Potato extends Vegetable { public Potato() { name = "potato"; }
public String getVegetable() { System.out.println("The vegetable is :" + name); return this.name; } }
|
將披薩修改成包含上述原料 (Sauce和Vegetable)
Pizza.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public abstract class Pizza { String name; Sauce sauce; Vegetable vegetable; public abstract void prepare();
public void bake() { System.out.println("Bake for 30 mins"); }
public void setName(String name) { this.name = name; }
public String getName() { return name; } }
|
定義兩個style的Pizza: 中式披薩(Chinese Pizza)和美式披薩(America Pizza),在這兩隻類別可以看到我們傳入了原料工廠,以決定哪種風格的披薩就用對應風格的原料工廠
ChinesePizza.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class ChinesePizza extends Pizza { PizzaIngredientFactory ingredientFactory;
public ChinesePizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; }
public void prepare() { System.out.println("Preparing: " + name);
sauce = ingredientFactory.createSauce(); sauce.getSauce();
vegetable = ingredientFactory.createVegetable(); vegetable.getVegetable(); } }
|
AmericaPizza.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class AmericaPizza extends Pizza { PizzaIngredientFactory ingredientFactory;
public AmericaPizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; }
public void prepare() { System.out.println("Preparing: " + name);
sauce = ingredientFactory.createSauce(); sauce.getSauce();
vegetable = ingredientFactory.createVegetable(); vegetable.getVegetable(); } }
|
接著再利用工廠方法,定義中式和美式的披薩工廠,製作出中式和美式的披薩
PizzaFactory.java
1 2 3 4 5 6 7 8 9 10
| public abstract class PizzaFactory { public void orderPizza(String type) { Pizza pizza = null; pizza = createPizza(type); pizza.prepare(); pizza.bake(); }
abstract Pizza createPizza(String type); }
|
AmericaPizzaFactory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class AmericaPizzaFactory extends PizzaFactory { protected Pizza createPizza(String style) { Pizza pizza = null; PizzaIngredientFactory ingredientFactory = new AmericaIngredientFactory();
if(style.equals("America")) { pizza = new AmericaPizza(ingredientFactory); pizza.setName("America style pizza"); }
return pizza; } }
|
:::info
可以在對美式工廠新增其他種不同口味的Pizza,用不同的原料工廠來達成
:::
ChinesePizzaFactory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class ChinesePizzaFactory extends PizzaFactory { protected Pizza createPizza(String style) { Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new ChineseIngredientFactory();
if(style.equals("Chinese")) { pizza = new ChinesePizza(ingredientFactory); pizza.setName("Chinese style pizza"); }
return pizza; } }
|
最後在Main.java中來製作披薩:
Main.java
1 2 3 4 5 6 7 8 9 10 11
| public class Main { public static void main(String[] args) { PizzaFactory AmericaPizzaFactory = new AmericaPizzaFactory(); AmericaPizzaFactory.orderPizza("America");
PizzaFactory ChinesePizzaFactory = new ChinesePizzaFactory();
ChinesePizzaFactory.orderPizza("Chinese"); } }
|
可以看到抽象工廠方法可以幫我們把相關產品集結起來(上述例子為原料們),可以產生出不同組的原料類別們,另外抽象工廠通常還會再搭配工廠方法,來建立其產品(披薩們,各個披薩會用到各個不同的抽象工廠所建立的原料工廠),如同該連結所做的->連結