設計模式學習筆記之工廠模式
本文講述一個披薩的誕生
我有一家披薩店,顧客來點了想吃的品種,然後我要準備材料、烘烤、剪下、幫顧客打包。這個過程用程式碼怎麼實現呢?
傳統方式
首先定義好準備、烘烤、剪下和打包這些動作
public abstract class Pizza { protected String name; public abstract void prepare(); public void bake() { System.out.println(name+" baking;"); } public void cut() { System.out.println(name+" cutting;"); } public void box() { System.out.println(name+" boxing;"); } public void setname(String name) { this.name=name; } } 複製程式碼
然後定義一下都可以做哪些披薩種類
public class CheesePizza extend Pizza{ @Override public void prepare(){ super.setname("CheesePizza"); System.out.println("CheesePizza"); } } 複製程式碼
public class GreekPizza extends Pizza { @Override public void prepare() { super.setname("GreekPizza"); System.out.println("GreekPizza"); } } 複製程式碼
準備工作做完,就等著顧客來點餐了
public class OrderPizza { public OrderPizza() { String type = null; Pizza pizza = null; do { type = getType(); if ("greek".equals(type)){ pizza = new GreekPizza(); }else if ("cheese".equals(type)){ pizza = new CheesePizza(); }else { break; } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); }while (true); } private String getType(){ String type = null; BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("input pizza type:"); type = br.readLine(); } catch (IOException e) { e.printStackTrace(); } return type; } } 複製程式碼
整個過程如下:
顧客根據披薩的種類進行點餐,然後根據型別進行建立披薩物件,進行準備、烘焙、剪下、打包
很明顯,這樣設計是有問題的。如果店裡有了新品,或者下架某種類披薩,就要修改OrderPizza類中根據顧客輸入來建立物件這段程式碼,顯然是違背上文提到的開放封閉原則。如下程式碼,現在我們已經知道哪些會改變,哪些不會改變,是時候使用封裝了。
//需要修改 if ("greek".equals(type)){ pizza = new GreekPizza(); else if ("cheese".equals(type)){ pizza = new CheesePizza(); }else { break; } //不需要修改 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); 複製程式碼
簡單披薩工廠
現在最好將建立物件移到orderPizza()之外,但怎麼做呢?我們可以把建立披薩的程式碼移到另一個物件中,由這個新物件專職建立披薩。 我們稱這個新物件為“工廠”。
工廠(factory)處理建立物件的細節。一旦有了SimplePizzaFactory,orderPizza()就變成此物件的客戶。當需要披薩時,就叫披薩工廠做一個。那些orderPizza()方法需要知道希臘披薩或者蛤蜊披薩的日子一去不復返了。現在orderPizza()方法只關心從工廠得到了一個披薩,而這個披薩實現了Pizza介面,所以它可以呼叫prepare()、bake()、cut()、box()來分別進行準備、烘烤、切片、裝盒。
SimplePizzaFactory是我們的新類,它負責為客戶建立披薩
public class SimpleFactoryPizza { public Pizza createPizza(String type){ Pizza pizza = null; if ("greek".equals(type)) { pizza=new GreekPizza(); }else if ("cheese".equals(type)) { pizza=new CheesePizza(); }else if ("beef".equals(type)) { pizza=new BeefPizza(); } return pizza; } } 複製程式碼
新的order只需構造時傳入一個工廠,然後帶入訂單型別來使用工廠建立披薩,代替之前具體的例項化
public class OrderPizza { SimpleFactoryPizza factory; public OrderPizza(SimpleFactoryPizza factory) { this.factory = factory; } Pizza orderPizza(String type) { Pizza pizza = null; pizza = factory.createPizza(type); if (pizza != null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } return pizza; } } 複製程式碼
雖然看似程式碼同傳統方式一樣,但是我們已經將變化的程式碼抽取出來了,在OrderPizza中我們無需再次修改,此時我們已經將變化的和不變化的隔離開來了。
工廠模式
如果披薩店生意越來越好,考慮開幾家加盟店。身為加盟公司的經營者,你希望確保加盟店的運營質量,還希望各地的披薩有自己不同的區域特點。在推廣SimpleFactoryPizza時,發現加盟店採用的統一的工廠建立的披薩,但是其他部分卻開始採用自創的流程,比如烘烤方式,包裝方式等等。那麼如果建立一個框架,約束關鍵步驟的同時又能保持一定的彈性呢?
修改給披薩店使用的框架
有一個辦法可以讓披薩製作活動侷限於OrderPizza類,同時讓不同的店還擁有自己的特色。
所要做的事情就是把createPizza()方法放回到OrderPizza中,不過得將它設定成抽象方法,然後為每個店鋪建立一個OrderPizza的子類。 來看下修改後的OrderPizza:
public abstract class OrderPizza { public Pizza orderPizza(String type){ Pizza pizza; //將建立方法從工廠物件中移回OrderPizza中 pizza = createPizza(type); //沒變過 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } //建立工廠方法改為抽象的,方便子類修改 abstract Pizza createPizza(String type); } 複製程式碼
現在已經有一個OrderPizza作為父類,讓不同區域的店鋪來繼承OrderPizza,每個子類各自決定製作什麼風味的披薩。
允許子類(不同店鋪)做決定
OrderPizza已經有一個不錯的訂單系統,由orderPizza()負責處理訂單,而你希望所有加盟店對於訂單的處理都能一致。 各個區域披薩店之間的差異在於他們製作披薩的風味(紐約披薩的薄脆、芝加哥披薩的餅厚等),我們現在要讓現在createPizza()能夠應對這些變化來負責建立正確種類的披薩。做法是讓OrderPizza的各個子類負責定義自己的createPizza()方法。所以我們會得到一些OrderPizza具體的子類,每個子類都有自己的披薩變體,而仍然適合OrderPizza框架,並使用除錯好的orderPizza()方法。
// 如果加盟店為顧客提供紐約風味的披薩,就使用NyStyleOrderPizza, // 因為此類的createPizza()方法會建立紐約風味的披薩 public class NyStyleOrderPizza extends OrderPizza{ //子類自己定義建立披薩方法 @Override Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new NyStyleCheesePizza(); } else if (type.equals("veggie")) { pizza = new NyStyleVeggiePizza(); } return pizza; } } // 類似的,利用芝加哥子類,我們得到了帶芝加哥原料的createPizza()實現 public class ChicagoStyleOrderPizza extends OrderPizza{ @Override Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new ChicagoCheesePizza(); } else if (type.equals("veggie")) { pizza = new ChicagoVeggiePizza(); } return pizza; } } 複製程式碼
現在問題來了,OrderPizza的子類終究只是子類,如何能夠做決定?在NyStyleOrderPizza類中,並沒有看到任何做決定邏輯的程式碼。 關於這個方面,要從OrderPizza類的orderPizza()方法觀點來看,此方法在抽象的OrderPizza內定義,但是隻在子類中實現具體型別。 orderPizza()方法對物件做了許多事情(例如:準備、烘烤、切片、裝盒),但由於Pizza物件是抽象的,orderPizza()並不知道哪些實際的具體類參與進來了。換句話說,這就是解耦(decouple)! 當orderPizza()呼叫createPizza()時,某個披薩店子類將負責建立披薩。做哪一種披薩呢?當然是由具體的披薩店決定。 那麼,子類是實時做出這樣的決定嗎?不是,但從orderPizza()的角度看,如果選擇在NyStyleOrderPizza訂購披薩,就是由這個子類(NyStyleOrderPizza)決定。嚴格來說,並非由這個子類實際做“決定”,而是由“顧客”決定哪一家風味的披薩店才決定了披薩的風味。
我們來看下如何呼叫:
public class test { public static void main(String[] args) { OrderPizza orderPizza = new NyStyleOrderPizza(); Pizza pi = orderPizza.orderPizza("cheese"); System.out.println("-------"); Pizza pizza = orderPizza.orderPizza("veggie"); } } 複製程式碼
執行結果如下:
Preparing NyStyleCheesePizza baking; Preparing NyStyleCheesePizza cutting; Preparing NyStyleCheesePizza boxing; ------- Preparing NyStyleVeggiePizza baking; Preparing NyStyleVeggiePizza cutting; Preparing NyStyleVeggiePizza boxing; 複製程式碼
本文來源:《head-first設計模式》