抽象工廠模式詳解 —— head first 設計模式
項目實例
假設你有一家 pizza 店,你有很多種 pizza,要在系統中顯示你所有 pizza 種類。實現這個功能並不難,使用普通方式實現:
public class PizzaStore { Pizza orderPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("clam")) { pizza = new ClamPizza(); } else if (type.equals("veggie")) { pizza = new VeggiePizza(); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }
}
這種把選擇披薩和製作過程全放在一起,如果新推出一款披薩就要修改 OrderPizza 方法,不符合開閉原則。
我們可已經這段變化的程式碼移到一個專門的類中,這個類只管創建 Pizza,我們將這個類的對象稱之為「工廠」;
簡單工廠(factory)
根據「將變化抽離並封裝「的原則,我們可以將創建 pizza 實例這一塊給抽離出來,因為這塊後邊可能會新增一些別的 pizza 類型,由一個對象來專職創建 pizza 對象。如果有一個 SimplePizzaFactory,那麼 orderPizza() 就變成此對象的客戶;
當客戶下單,pizzaStore 調用 orderPizza() 的時候,就叫比薩工廠做一個,orderPizza() 只關心從工廠得到一個比薩;
public class SimplePizzaFactory { public PizzaCreatePizza(string pizzaType){ Pizza pizza = null; if(pizzaType.Equals("cheese")) pizza = newCheesePizza(); else if (pizzaType.Equals("pepperoni")) pizza = newPepperoniPizza(); else if(pizzaType.Equals("clam")) pizza = newCalmPizza(); else if(pizzaType.Equals("veggie")) pizza = newVeggiePizza(); return pizza; } }
接下來看下 Pizza 類:
public class Pizza { Pizza OrderPizza(stringpizzaType){ Pizza pizza; SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();//生成pizza pizza =simplePizzaFactory.CreatePizza(pizzaType); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }
這樣的話,就可以遵循了把變化的部分抽象成一個類。下次需要變化,只需要對變化的類的部分做修改即可。
下面給出其UML圖。
簡單工廠的目的,主要負責實現生產對象的細節,根據訂單來生產。每一個商店有對應著自己的工廠。在此,可以將 OrderPizza 是工廠的一個客戶,當然還可以是其他類似的方法,比如 PizzaShopMenu 也可以是工廠的客戶。客戶就是為了獲取工廠生成的對象。前者是為了賣給 people,後者是為了使用 Pizza 類獲取其描述和價格等。這樣遵循了找出變化的部分,將其封裝到一個類中的原則,從而到達了復用的目的。
簡單工廠之所以簡單是因為它只根據需要,來生產出來指定的 Pizza。
特點:所有的產品都由一個工廠來生產。
上面的簡單工廠並不是一個真正的模式,只是一種編程習慣,這個不能算工廠模式,不過也算是基本滿足需求。
接下來我們來看看工廠方法的實現。
工廠方法
背景更新:
假如現在你要開分店,各種加盟商進來後,他們都要開發符合本地口味的 pizza,那就需要各個地方都有一個工廠,也就是每個地方繼承 SimpleFactory類,但是每個工廠並不是完全使用你原來的烘培方法。或許,我們可以像 1 中那樣利用簡單的工廠,對應不同地區的加盟店創建不同的工廠。
這樣做導致的另一個問題就是,不同的加盟店披薩的製作流程、方法可能不同,如何才能把加盟店和創建披薩捆綁在一起的同時又保持一定的彈性?
我們可以把 CreatePizza()方法放回到 PizzaStore 中,但是要把它設置為抽象方法,然後為每一個區域加盟店創建一個 PizzaStore 的子類。
如下所示:
對應的程式碼如下:
public abstract class PizzaStore { public PizzaOrderPizza(string pizzaType) { Pizza pizza =CreatePizza(pizzaType); pizza.Prepare(); pizza.Bake(); pizza.Cut(); pizza.Box(); return pizza; } public abstract Pizza CreatePizza(string pizzaType); //把工廠對象移到該方法中,該方法為抽象方法 }
對應於兩家分店的程式碼:
public class NYPizzaStore extends PizzaStore { @Override protected Pizza createPizza(String type) { if (type.equals("cheese")) { return new NYCheesePizza(); } return null; } } public class ChicagoPizzaStore extends PizzaStore { @Override protected Pizza createPizza(String type) { if (type.equals("cheese")) { return new ChicagoCheesePizza(); } return null; } }
我們需要建立一個 Pizza 實體類:
public abstract class Pizza { String name; //名稱 String dough; //麵糰類型 String sauce; //醬料 ArrayList<String> toppings = new ArrayList<String>(); //作料 void prepare() { System.out.println("準備 " + name); System.out.println("揉麵糰..."); System.out.println("添加醬料..."); System.out.println("添加作料: "); for (int i = 0; i < toppings.size(); i++) { System.out.println(" " + toppings.get(i)); } } void bake() { System.out.println("烘烤25分鐘"); } void cut() { System.out.println("把Pizza對角切片"); } void box() { System.out.println("把Pizza裝盒子"); } public String getName() { return name; } }
然後需要一些具體的子類,下邊定義兩個子類:紐約風味的芝士披薩(NYStyleCheesePizza)、芝加哥風味的芝士披薩 (ChicageStyleCheesePizza)
public class NYStyleCheesePizza extends Pizza { public NYStyleCheesePizza() { name = "NY Style Sauce and Cheese Pizza"; dough = "Thin Crust Dough"; sauce = "Marinara Sauce"; toppings.add("Grated Reggiano Cheese"); } } public class ChicagoStyleCheesePizza extends Pizza { public ChicagoStyleCheesePizza() { name = "Chicago Style Deep Dish Cheese Pizza"; dough = "Extra Thick Crust Dough"; sauce = "Plum Tomato Sauce"; toppings.add("Shredded Mozzarella Cheese"); } //可以覆蓋cut()方法 void cut() { System.out.println("Cutting the pizza into square slices"); } }
2.2 總結
所有工廠模式都用來封裝對象創建,工廠方法模式通過讓子類決定該創建的對象是什麼,來達到將對象創建的過程封裝的目的。
工廠方法模式定義了一個創建對象的介面,但由子類決定要實例化的類是哪一個,工廠方法讓類把實例化推遲到子類。
創建者(Creator)類
產品類
簡單工廠和工廠方法之間的差異?
簡單工廠是在一個地方把所有的事都處理完了,然而工廠方法卻是創建一個框架,讓子類決定要如何實現。簡單工廠的做法,可以將對象的創建封裝起來,但是簡單工廠不具備工廠方法的彈性,因為簡單工廠不能變更正在創建的產品。
抽象工廠
創建工廠介面
回到上文的 Pizza 店,現在有新的需求,想要確保每家加盟店使用高品質的材料,打算創建一家生產原料的加工廠,並將原料送到各個加盟店。這個工廠負責創建原料家族中的每一種原料,工廠需要生產麵糰、醬料、芝士等。先為工廠定義一個介面,該介面負責所有原料:
public interface PizzaIngredientFactory { Dough CreateDough(); Sauce CreateSauce(); Cheese CreateCheese(); Veggies[] CreateVeggies(); Pepperoni CreatePepperoni(); Clams CreateClam(); }
創建一個原料廠
//具體原料工廠必須實現這個介面 public class NYPizzaIngredientFactory extends PizzaIngredientFactory { public Dough CreateDough() { return new ThinCrustDough(); } public Sauce CreateSauce() { return new MarinaraSauce(); } public Cheese CreateCheese() { return new ReggianoCheese(); } public Veggies[] CreateVeggies() { Veggies[] veggies = new Veggies[]{ new Garlic(), new Onion(), new Mushroom(), new RedPepper() }; return veggies; } public Pepperoni CreatePepperoni() { return new SlicedPepperoni(); } public Clams CreateClam() { return new FreshClams(); } }
重新抽象 Pizza 類
public abstract class Pizza { public string name; public Dough dough; public Sauce sauce; public Veggies[] veggies; public Cheese cheese; public Pepperoni pepperoni; public Clams clam; public ArrayList toppings = newArrayList(); public abstract void Prepare();//把Prepare()方法聲明成抽象,在這個方法中,我們需要收集Pizza所需的原材料,而這些原材料來自原料工廠。
public void Bake() { System.Console.WriteLine("Bakefor 25 minutes at 350"); } public void Cut() { System.Console.WriteLine("Cuttingthe pizza into diagonal slices"); } public void Box() { System.Console.WriteLine("Placepizza in official PizzaStore box"); } public string GetName() { return name; } }
重新實現 Pizza
public class NYStyleCheesePizza extends Pizza { PizzaIngredientFactory ingredientFactory;
public NYStyleCheesePizza(PizzaIngredientFactoryingredientFactory){
//製作Pizza需要工廠提供原材料,所以每個pizza類都需要從構造器中得到一個工廠,並將工廠存儲在變數中 this.ingredientFactory =ingredientFactory; name = "NY StyleSauc and Cheese Pizza"; toppings.Add("GratedReggiano Cheese"); } public override void Prepare() { System.Console.WriteLine("Preparing" + name); dough = ingredientFactory.CreateDough(); sauce = ingredientFactory.CreateSauce(); cheese = ingredientFactory.CreateCheese(); } }
重新生產pizza
public class MYPizzaStore extends PizzaStore { public override Pizza CreatePizza(string type) { Pizza pizza=null; PizzaIngredientFactory ingredientFactory= new NYPizzaIngredientFactory(); switch(type) { case "cheese": pizza = new NYStyleCheesePizza(ingredientFactory); break; case "veggie": pizza=new NYStyleVeggiePizza(ingredientFactory); break; case "clam": pizza=new NYStyleClamPizza(ingredientFactory); break; case "pepperoni": pizza=new NYStylePepperoniPizza(ingredientFactory); break; } return pizza; } }
通過這一系列的操作,我們引入了新類型的工廠,也就是所謂的「抽象工廠」,來創建 pizza 原來家族。通過抽象工廠所提供的介面創建產品家族,利用這個介面書寫程式碼,我們的程式碼將從實際工廠解耦,以便在不同上下文中實現各式各樣的工廠,製造出各種不同的產品。
定義抽象工廠模式
抽象工廠模式提供一個介面,用於創建相關或依賴對象家族,而且不需要致命具體類。
抽象工廠模式允許客戶使用抽象的介面來創建一組相關的產品,而不需要知道實際產出的具體產品是什麼,客戶就從具體的產品中被解耦。
抽象工廠與工廠方法的對比
抽象工廠和工廠方法都是負責創建對象。
抽象工廠是通過對象的組合
定義了一個創建對象的介面,但由於子類決定要實例化的類是哪一個,工廠方法讓類把實例化推遲到子類。
所以利用工廠方法創建對象時,需要擴展一個類,並覆蓋它的工廠方法。
整個工廠方法模式,只不過就是通過子類來創建對象,這種做法,客戶只需要知道他們所使用的抽象類型就可以了,而由子類負責決定具體類型。將客戶從具體類型中解耦。
工廠方法是繼承。
抽象工廠方法是將一群相關的產品集合起來,用於創建相關或依賴對象的家族,而不需要明確指定具體類。
參考文章
//blog.csdn.net/xuemoyao/article/details/53437609
//www.cnblogs.com/lzhp/p/3375041.html