一、問題
在前一章《設計模式讀書筆記-----簡單工廠模式》中通過披薩的實例介紹了簡單工廠模式。在披薩實例中,如果我想根據地域的不同生產出不同口味的披薩,如紐約口味披薩,芝加哥口味披薩。如果利用簡單工廠模式,我們需要兩個不同的工廠,NYPizzaFactory、ChicagoPizzaFactory。在該地域中有很多的披薩店,他們並不想依照總店的制作流程來生成披薩,而是希望采用他們自己的制作流程。這個時候如果還使用簡單工廠模式,因為簡單工廠模式是將披薩的制作流程完全承包了。那么怎么辦?
二、解決方案
我們可以這樣解決:將披薩的制作方法交給各個披薩店完成,但是他們只能提供制作完成的披薩,披薩的訂單處理仍然要交給披薩工廠去做。也就是說,我們將createPizza()方法放回到PizzaStore中,其他的部分還是保持不變。
三、基本定義
工廠方法模式定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法模式讓實例化推遲到子類。
四、模式結構
工廠方法模式的UML結構圖:
Product:抽象產品。所有的產品必須實現這個共同的接口,這樣一來,使用這些產品的類既可以引用這個接口。而不是具體類。
ConcreteProduct:具體產品。
Creator:抽象工廠。它實現了所有操縱產品的方法,但不實現工廠方法。Creator所有的子類都必須要實現factoryMethod()方法。
ConcreteCreator:具體工廠。制造產品的實際工廠。它負責創建一個或者多個具體產品,只有ConcreteCreator類知道如何創建這些產品。
工廠方法模式是簡單工廠模式的延伸。在工廠方法模式中,核心工廠類不在負責產品的創建,而是將具體的創建工作交給子類去完成。也就是后所這個核心工廠僅僅只是提供創建的接口,具體實現方法交給繼承它的子類去完成。當我們的系統需要增加其他新的對象時,我們只需要添加一個具體的產品和它的創建工廠即可,不需要對原工廠進行任何修改,這樣很好地符合了“開閉原則”。
五、工廠方法模式實現
針對上面的解決方案,得到如下UML結構圖:
抽象產品類:Pizza.java
1 public abstract class Pizza { 2 protected String name; //名稱 3 protected String dough; //面團 4 protected String sause; //醬料 5 protected List<String> toppings = new ArrayList<String>(); //佐料 6 7 8 public void prepare() { 9 System.out.println("Preparing "+name); 10 System.out.println("Tossing dough"); 11 System.out.println("Adding sause"); 12 System.out.println("Adding toppings"); 13 for(int i = 0;i < toppings.size();i++){ 14 System.out.println(" "+toppings.get(i)); 15 } 16 } 17 18 public void bake() { 19 System.out.println("Bake for 25 minutes at 350"); 20 } 21 22 public void cut() { 23 System.out.println("Cutting the pizza into diagonal slices"); 24 } 25 26 public void box() { 27 System.out.println("Place pizza in official PizzaStore box"); 28 } 29 30 public String getName(){ 31 return name; 32 } 33 }
具體產品類:NYStyleCheesePizza.java
1 public class NYStyleCheesePizza extends Pizza{ 2 public NYStyleCheesePizza(){ 3 name = "Ny Style Sauce and Cheese Pizza"; 4 dough = "Thin Crust Dough"; 5 sause = "Marinara Sauce"; 6 7 toppings.add("Crated Reggiano Cheese"); 8 } 9 10 }
ChicagoStyleCheesePizza.java
1 public class ChicagoStyleCheesePizza extends Pizza { 2 public ChicagoStyleCheesePizza(){ 3 name = "Chicago Style Deep Dish Cheese Pizza"; 4 dough = "Extra Thick Crust Dough"; 5 sause = "Plum Tomato Sauce"; 6 7 toppings.add("Shredded Mozzarella Cheese"); 8 } 9 10 public void cut(){ 11 System.out.println("Cutting the Pizza into square slices"); 12 } 13 }
抽象工廠:披薩總店。PizzaStore.java
1 public abstract class PizzaStore { 2 public Pizza orderPizza(String type){ 3 Pizza pizza; 4 pizza = createPizza(type); 5 6 pizza.prepare(); 7 pizza.bake(); 8 pizza.cut(); 9 pizza.box(); 10 11 return pizza; 12 } 13 14 /* 15 * 創建pizza的方法交給子類去實現 16 */ 17 abstract Pizza createPizza(String type); 18 }
具體工廠。披薩分店。NYPizzaStore.java
1 public class NYPizzaStore extends PizzaStore{ 2 3 @Override 4 Pizza createPizza(String item) { 5 Pizza pizza = null; 6 if("cheese".equals(item)){ 7 pizza = new NYStyleCheesePizza(); 8 } 9 else if("veggie".equals(item)){ 10 pizza = new NYStyleVeggiePizza(); 11 } 12 else if("clam".equals(item)){ 13 pizza = new NYStyleClamPizza(); 14 } 15 else if("pepperoni".equals(item)){ 16 pizza = new NYStylePepperoniPizza(); 17 } 18 19 return pizza; 20 }
ChicagoPizzaStore.java
1 public class ChicagoPizzaStore extends PizzaStore { 2 Pizza createPizza(String type) { 3 Pizza pizza = null; 4 if("cheese".equals(type)){ 5 pizza = new ChicagoStyleCheesePizza(); 6 } 7 else if("clam".equals(type)){ 8 pizza = new ChicagoStyleClamPizza(); 9 } 10 else if("pepperoni".equals(type)) { 11 pizza = new ChicagoStylePepperoniPizza(); 12 } 13 else if("veggie".equals(type)){ 14 pizza = new ChicagoStyleVeggiePizza(); 15 } 16 return pizza; 17 } 18 19 }
做了這么多,應該可以吃披薩了吧。Ethan要一份紐約口味的披薩,Joel需要芝加哥口味的披薩。
PizzaTestDrive.java
1 public class PizzaTestDrive { 2 public static void main(String[] args) { 3 System.out.println("---------Joel 需要的芝加哥的深盤披薩---------"); 4 ChicagoPizzaStore chicagoPizzaStore = new ChicagoPizzaStore(); //建立芝加哥的披薩店 5 Pizza joelPizza =chicagoPizzaStore.orderPizza("cheese"); //下訂單 6 System.out.println("Joel ordered a " + joelPizza.getName() + "\n"); 7 8 System.out.println("---------Ethan 需要的紐約風味的披薩---------"); 9 NYPizzaStore nyPizzaStore = new NYPizzaStore(); 10 Pizza ethanPizza = nyPizzaStore.orderPizza("cheese"); 11 System.out.println("Ethan ordered a " + ethanPizza.getName() + "\n"); 12 13 } 14 }
運行結果。
六、工廠方法模式的優缺點
優點
1、 在工廠方法中,用戶只需要知道所要產品的具體工廠,無須關系具體的創建過程,甚至不需要具體產品類的類名。
2、 在系統增加新的產品時,我們只需要添加一個具體產品類和對應的實現工廠,無需對原工廠進行任何修改,很好地符合了“開閉原則”。
缺點
1、 每次增加一個產品時,都需要增加一個具體類和對象實現工廠,是的系統中類的個數成倍增加,在一定程度上增加了系統的復雜度,同時也增加了系統具體類的依賴。這並不是什么好事。
七、工廠方法適用場景
1、一個類不知道它所需要的對象的類。在工廠方法模式中,我們不需要具體產品的類名,我們只需要知道創建它的具體工廠即可。
2、一個類通過其子類來指定創建那個對象。在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。
3、將創建對象的任務委托給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定。
七、總結
1、工廠方法模式完全符合“開閉原則”。
2、工廠方法模式使用繼承,將對象的創建委托給子類,通過子類實現工廠方法來創建對象。
3、工廠方法允許類將實例化延伸到子類進行。
4、工廠方法讓子類決定要實例化的類時哪一個。在這里我們要明白這並不是工廠來決定生成哪種產品,而是在編寫創建者類時,不需要知道實際創建的產品是哪個,選擇了使用哪個子類,就已經決定了實際創建的產品時哪個了。
5、在工廠方法模式中,創建者通常會包含依賴於抽象產品的代碼,而這些抽象產品是、由子類創建的,創建者不需要真的知道在制作哪種具體產品。