工廠方法模式定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到了子類。
假設你有一個披薩店,預定披薩的代碼可能是這么寫的:
Pizza orderPizza(){ Pizza pizza = new Pizza(); // 準備面皮,加調料等 pizza.PRepare(); // 烘烤 pizza.bake(); // 切片 pizza.cut(); // 裝盒 pizza.box(); return pizza;}更多的披薩類型
但是,你現在需要更多的披薩類型。你必須增加一些代碼,來“決定”適合的披薩類型,然后再“制造”這個披薩:
/ 現在把披薩類型傳入orderPizzaPizza orderPizza(String type) { Pizza pizza; // 根據披薩的類型,我們實例化正確的具體類,然后將其賦值給pizza實例變量。 // 請注意,這里的任何披薩都必須實現pizza接口。 if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("greek")) { pizza = new GreekPizza(); } else if (type.equals("pepperoni")) { pizza = new Pepperonipizza(); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza;}修改披薩類型
你發現你所有的競爭者都已經在他們的菜單中加入了一些流行風味的披薩:Clam Pizza(蛤蜊披薩)、Veggie Pizza(素食披薩)。很明顯,你必須要趕上他們,所以也要把這些風味加進你的菜單中。而最近Greek Pizza(希臘披薩)賣得不好,所以你決定將它從菜單中去掉:
Pizza orderPizza(String type) { Pizza pizza; // 此代碼沒有對修改封閉。如果披薩店改變它所供應的披薩風味,就得進到這里進行修改。 if (type.equals("cheese")) { pizza = new CheesePizza();// } else if (type.equals("greek")) {// pizza = new GreekPizza(); } else if (type.equals("pepperoni")) { pizza = new PepperoniPizza(); } 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()出問題,而且也無法讓orderPizza()對修改關閉。但是,現在我們已經知道哪些會改變,哪些不會改變,該是使用封裝的時候了。建立一個簡單披薩工廠
現在最好將創建對象移到orderPizza()之外,但怎么做呢?我們可以把創建披薩的代碼移到另一個對象中,由這個新對象專職創建披薩。我們稱這個新對象為“工廠”。工廠(factory)處理創建對象的細節。一旦有了SimplePizzaFactory,orderPizza()就變成此對象的客戶。當需要披薩時,就叫披薩工廠做一個。那些orderPizza()方法需要知道希臘披薩或者蛤蜊披薩的日子一去不復返了?,F在orderPizza()方法只關心從工廠得到了一個披薩,而這個披薩實現了Pizza接口,所以它可以調用prepare()、bake()、cut()、box()來分別進行準備、烘烤、切片、裝盒。
/ SimplePizzaFactory是我們的新類,它只做一件事情:幫它的客戶創建披薩public class SimplePizzaFactory { // 首先,在這個工廠內定義一個createPizza()方法,所有客戶用這個方法來實例化新對象。 public Pizza createPizza(String type) { Pizza pizza = null; // 這是從orderPizza()方法中移過來的代碼 if (type.equals("cheese")) { pizza = new CheesePizza(); } else if (type.equals("pepperoni")) { pizza = new PepperoniPizza(); } else if (type.equals("clam")) { pizza = new ClamPizza(); } else if (type.equals("veggie")) { pizza = new VeggiePizza(); } return pizza; }} // 現在我們為PizzaStore加上一個對SimplePizzaFactory的引用public class PizzaStore { SimplePizzaFactory factory; // PizzaStore的構造器,需要一個工廠作為參數 public PizzaStore(SimplePizzaFactory factory){ this.factory = factory; } public Pizza orderPizza(String type) { Pizza pizza; // orderPizza()方法通過簡單傳入訂單類型來使用工廠創建披薩。 // 請注意,我們把new操作符替換成工廠對象的創建方法。這里不再使用具體實例化 pizza = factory.createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }}加盟披薩店
你的披薩店經營有成,擊敗了競爭者,現在大家都希望能在自家附近有加盟店。身為加盟公司經營者,你希望確保加盟店運營的質量,所以希望這些店都使用你那些經過時間考驗的代碼。但是區域的差異呢?每家加盟店都可能想要提供不同風味的披薩(比方說紐約、芝加哥、加州),這受到了開店地點及該地區披薩美食家口味的影響。在推廣SimplePizzaFactory時,你發現加盟店的確是采用你的工廠創建披薩,但是其他部分,卻開始采用他們自創的流程:烘烤的做法有差異、不要切片、使用其他廠商的盒子。能不能建立一個框架,把加盟店和創建披薩捆綁在一起的同時又保持一定的彈性?
給披薩店使用的框架
有個做法可讓披薩制作活動局限于PizzaStore類,而同時又能讓這些加盟店依然可以自由地制作該區域的風味。所要做的事情,就是把createPizza()方法放回到PizzaStore中,不過要把它設置成“抽象方法”,然后為每個區域風味創建一個PizzaStore的子類。首先,看PizzaStore所做的改變:
public abstract class PizzaStore { public Pizza orderPizza(String type) { Pizza pizza; // 現在createPizza()方法從工廠對象中移回PizzaStore pizza = createPizza(type); // 這些都沒變 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } // 現在把工廠對象移到這個方法中 // 在PizzaStore里,“工廠方法”現在是抽象的 abstract Pizza createPizza(String type);}現在已經有一個PizzaStore作為超類;讓每個域類型(NYPizzaStore、ChicagoPizzaStore、CaliforniaPizzaStore)都繼承這個PizzaStore,每個子類各自決定如何制作披薩。允許子類做決定
PizzaStore已經有一個不錯的訂單系統,由orderPizza()方法負責處理訂單,而你希望所有加盟店對于訂單的處理都能一致。各個區域披薩店之間的差異在于他們制作披薩的風味(紐約披薩的薄脆、芝加哥披薩的餅厚等),我們現在要讓現在createPizza()能夠應對這些變化來負責創建正確種類的披薩。做法是讓PizzaStore的各個子類負責定義自己的createPizza()方法。所以我們會得到一些PizzaStore具體的子類,每個子類都有自己的披薩變體,而仍然適合PizzaStore框架,并使用調試好的orderPizza()方法
public abstract class PizzaStore { public Pizza orderPizza(String type) { Pizza pizza; pizza = createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } // 每個子類都會覆蓋createPizza()方法 abstract Pizza createPizza(String type);} // 如果加盟店為顧客提供紐約風味的披薩,就使用NyStylePizzaStore,// 因為此類的createPizza()方法會建立紐約風味的披薩public class NyStylePizzaStore extends PizzaStore{ @Override Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new NyStyleCheesePizza(); } else if (type.equals("pepperoni")) { pizza = new NyStylePepperoniPizza(); } else if (type.equals("clam")) { pizza = new NyStyleClamPizza(); } else if (type.equals("veggie")) { pizza = new NyStyleVeggiePizza(); } return pizza; }} // 類似的,利用芝加哥子類,我們得到了帶芝加哥原料的createPizza()實現public class ChicagoStylePizzaStore extends PizzaStore{ @Override Pizza createPizza(String type) { Pizza pizza = null; if (type.equals("cheese")) { pizza = new ChicagoCheesePizza(); } else if (type.equals("pepperoni")) { pizza = new ChicagoPepperoniPizza(); } else if (type.equals("clam")) { pizza = new ChicagoClamPizza(); } else if (type.equals("veggie")) { pizza = new ChicagoVeggiePizza(); } return pizza; }}現在問題來了,PizzaStore的子類終究只是子類,如何能夠做決定?在NyStylePizzaStore類中,并沒有看到任何做決定邏輯的代碼。關于這個方面,要從PizzaStore的orderPizza()方法觀點來看,此方法在抽象的PizzaStore內定義,但是只在子類中實現具體類型。orderPizza()方法對對象做了許多事情(例如:準備、烘烤、切片、裝盒),但由于Pizza對象是抽象的,orderPizza()并不知道哪些實際的具體類參與進來了。換句話說,這就是解耦(decouple)!當orderPizza()調用createPizza()時,某個披薩店子類將負責創建披薩。做哪一種披薩呢?當然是由具體的披薩店決定。那么,子類是實時做出這樣的決定嗎?不是,但從orderPizza()的角度看,如果選擇在NyStylePizzaStore訂購披薩,就是由這個子類(NyStylePizzaStore)決定。嚴格來說,并非由這個子類實際做“決定”,而是由“顧客”決定哪一家風味的披薩店才決定了披薩的風味。工廠方法模式
工廠方法模式定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到了子類。工廠方法模式(Factory Method Pattern)通過讓子類決定該創建的對象是什么,來達到將對象創建的過程封裝的目的。PizzaStore就是創建者(Creator)類。它定義了一個抽象的工廠方法,讓子類實現此方法制造產品。創建者通常會包含依賴于抽象產品的代碼,而這些抽象產品由子類制造。創建者不需要真的知道在制造哪種具體產品。能夠產生產品的類稱為具體創建者。NYPizzaStore和ChicagoPizzaStore就是具體創建者。Pizza是產品類。工廠生產產品,對PizzaStore來說,產品就是Pizza。抽象的Creator提供了一個創建對象的方法的接口,也稱為“工廠方法”。在抽象的Creator中,任何其他實現的方法,都可能使用到這個工廠方法所制造出來的產品,但只有子類真正實現這個工廠方法并創建產品。
// Creator是一個類,它實現了所有操縱產品的方法,但不實現工廠方法public abstract class Creator{ void anOperation(){ // ... } // Creator的所有子類都必須實現這個抽象的factoryMethod()方法 abstract void factoryMethod();} // 具體的創建者public class ConcreteCreator extends Creator{ // ConcreteCreator實現了factoryMethod(),以實際制造出產品。 @Override void factoryMethod() { // ... }} // 所有產品必須實現這個接口,這樣一來,// 使用這些產品的類就可以引用這個接口,而不是具體的類public abstract class Product{ void operation(){ // ... }} // 具體的產品public class ConcreteProduct extends Product{}依賴倒置原則
假設你從未聽說過OO工廠。下面是一個不使用工廠模式的披薩店版本。數一數,這個類所依賴的具體披薩對象有幾種。
public class DependentPizzaStore { public Pizza createPizza(String style, String type) { Pizza pizza = null; if(style.equals("NY")){ // 處理所有紐約風味的披薩 if (type.equals("cheese")) { pizza = new NyStyleCheesePizza(); } else if (type.equals("pepperoni")) { pizza = new NyStylePepperoniPizza(); } else if (type.equals("clam")) { pizza = new NyStyleClamPizza(); } else if (type.equals("veggie")) { pizza = new NyStyleVeggiePizza(); } } else if(style.equals("Chicago")){ // 處理所有芝加哥風味的披薩 if (type.equals("cheese")) { pizza = new ChicagoCheesePizza(); } else if (type.equals("pepperoni")) { pizza = new ChicagoPepperoniPizza(); } else if (type.equals("clam")) { pizza = new ChicagoClamPizza(); } else if (type.equals("veggie")) { pizza = new ChicagoVeggiePizza(); } } else { System.out.println("Error"); return null; } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }}如果把這個版本的披薩店和它依賴的對象畫成一張圖,看起來是這樣的:
這個版本的PizzaStore依賴于所有的披薩對象,因為它直接創建這些披薩對象。如果這些類的實現改變了,那么可能必須修改PizzaStore。每新增一個披薩類型,就等于讓PizzaStore多了一個依賴。因為對于披薩具體實現的任何改變都會影響到PizzaStore,我們說PizzaStore“依賴于”披薩的實現。很清楚的,代碼里減少對于具體類的依賴是件好事。有一個OO設計原則就正式闡明了這一點:
依賴倒置原則:要依賴抽象,不要依賴具體類。
這個原則說明了:不能讓高層組件依賴低層組件,而且,不管高層或低層組件,兩者都應該依賴于抽象。比如,這個例子里的PizzaStore是高層組件,而披薩實現是低層組件,很清楚的,PizzaStore依賴這些具體披薩類。現在,這個原則告訴我們,應該重寫代碼以便于我們依賴抽象類,而不依賴具體類。對于高層及低層模塊都應該如此。
依賴倒置原則的應用
非常依賴披薩店的主要問題在于:它依賴每個披薩類型。因為它是在自己的orderPizza()方法中,實例化這些具體類型的。如何在orderPizza()方法中,將這些實例化對象的代碼獨立出來?我們知道,工廠方法剛好能派上用場。所以,應用工廠方法后,類圖看起來就像這樣:
PizzaStore現在依賴Pizza這個抽象類。具體披薩類也依賴Pizza抽象,因為它們實現了Pizza接口。在應用工廠方法后,高層組件(也就是PizzaStore)和低層組件(也就是這些披薩)都依賴了Pizza抽象。想要遵循依賴倒置原則,工廠方法并非是唯一的技巧,但卻是最有威力的技巧之一。
遵循依賴倒置原則的指導方針
下面的指導方針,能幫你避免在OO設計中違反依賴倒置原則:
變量不可以持有具體類的引用
如果使用new,就會持有具體類的引用。你可以改用工廠來避開這樣的做法。
不要讓類派生自具體類
如果派生自具體類,你就會依賴具體類。請派生自一個抽象(接口或抽象類)。
不要覆蓋基類中已實現的方法
如果覆蓋基類已實現的方法,那么你的基類就不是一個真正適合被繼承的抽象?;愔幸褜崿F的方法,應該由所有的子類共享。要完全遵守這些指導方針似乎不太可能,但是如果你深入體驗這些方針,將這些方針內化成你思考的一部分,那么在設計時,你將知道何時有足夠的理由違反這樣的原則。比方說,如果有一個不像是會改變的類,那么在代碼中直接實例化具體類也就沒什么大礙。另一方面,如果有個類可能改變,你可以采用一些好技巧(例如工廠方法)來封裝改變。
原文出處: cashow
新聞熱點
疑難解答