工廠方法模式定義了一個創(chuàng)建對象的接口,但由子類決定要實(shí)例化的類是哪一個。工廠方法讓類把實(shí)例化推遲到了子類。
假設(shè)你有一個披薩店,預(yù)定披薩的代碼可能是這么寫的:
Pizza orderPizza(){ Pizza pizza = new Pizza(); // 準(zhǔn)備面皮,加調(diào)料等 pizza.PRepare(); // 烘烤 pizza.bake(); // 切片 pizza.cut(); // 裝盒 pizza.box(); return pizza;}更多的披薩類型
但是,你現(xiàn)在需要更多的披薩類型。你必須增加一些代碼,來“決定”適合的披薩類型,然后再“制造”這個披薩:
/ 現(xiàn)在把披薩類型傳入orderPizzaPizza orderPizza(String type) { Pizza pizza; // 根據(jù)披薩的類型,我們實(shí)例化正確的具體類,然后將其賦值給pizza實(shí)例變量。 // 請注意,這里的任何披薩都必須實(shí)現(xiàn)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;}修改披薩類型
你發(fā)現(xiàn)你所有的競爭者都已經(jīng)在他們的菜單中加入了一些流行風(fēng)味的披薩:Clam Pizza(蛤蜊披薩)、Veggie Pizza(素食披薩)。很明顯,你必須要趕上他們,所以也要把這些風(fēng)味加進(jìn)你的菜單中。而最近Greek Pizza(希臘披薩)賣得不好,所以你決定將它從菜單中去掉:
Pizza orderPizza(String type) { Pizza pizza; // 此代碼沒有對修改封閉。如果披薩店改變它所供應(yīng)的披薩風(fēng)味,就得進(jìn)到這里進(jìn)行修改。 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(); } // 這里是我們不想改變的地方。因?yàn)榕_的準(zhǔn)備、烘烤、包裝,多年來持續(xù)不變, // 所以這部分的代碼不會改變,只有發(fā)生這些動作的披薩會改變。 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza;}很明顯地,如果實(shí)例化“某些”具體類,將使orderPizza()出問題,而且也無法讓orderPizza()對修改關(guān)閉。但是,現(xiàn)在我們已經(jīng)知道哪些會改變,哪些不會改變,該是使用封裝的時(shí)候了。建立一個簡單披薩工廠
現(xiàn)在最好將創(chuàng)建對象移到orderPizza()之外,但怎么做呢?我們可以把創(chuàng)建披薩的代碼移到另一個對象中,由這個新對象專職創(chuàng)建披薩。我們稱這個新對象為“工廠”。工廠(factory)處理創(chuàng)建對象的細(xì)節(jié)。一旦有了SimplePizzaFactory,orderPizza()就變成此對象的客戶。當(dāng)需要披薩時(shí),就叫披薩工廠做一個。那些orderPizza()方法需要知道希臘披薩或者蛤蜊披薩的日子一去不復(fù)返了。現(xiàn)在orderPizza()方法只關(guān)心從工廠得到了一個披薩,而這個披薩實(shí)現(xiàn)了Pizza接口,所以它可以調(diào)用prepare()、bake()、cut()、box()來分別進(jìn)行準(zhǔn)備、烘烤、切片、裝盒。
/ SimplePizzaFactory是我們的新類,它只做一件事情:幫它的客戶創(chuàng)建披薩public class SimplePizzaFactory { // 首先,在這個工廠內(nèi)定義一個createPizza()方法,所有客戶用這個方法來實(shí)例化新對象。 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; }} // 現(xiàn)在我們?yōu)镻izzaStore加上一個對SimplePizzaFactory的引用public class PizzaStore { SimplePizzaFactory factory; // PizzaStore的構(gòu)造器,需要一個工廠作為參數(shù) public PizzaStore(SimplePizzaFactory factory){ this.factory = factory; } public Pizza orderPizza(String type) { Pizza pizza; // orderPizza()方法通過簡單傳入訂單類型來使用工廠創(chuàng)建披薩。 // 請注意,我們把new操作符替換成工廠對象的創(chuàng)建方法。這里不再使用具體實(shí)例化 pizza = factory.createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }}加盟披薩店
你的披薩店經(jīng)營有成,擊敗了競爭者,現(xiàn)在大家都希望能在自家附近有加盟店。身為加盟公司經(jīng)營者,你希望確保加盟店運(yùn)營的質(zhì)量,所以希望這些店都使用你那些經(jīng)過時(shí)間考驗(yàn)的代碼。但是區(qū)域的差異呢?每家加盟店都可能想要提供不同風(fēng)味的披薩(比方說紐約、芝加哥、加州),這受到了開店地點(diǎn)及該地區(qū)披薩美食家口味的影響。在推廣SimplePizzaFactory時(shí),你發(fā)現(xiàn)加盟店的確是采用你的工廠創(chuàng)建披薩,但是其他部分,卻開始采用他們自創(chuàng)的流程:烘烤的做法有差異、不要切片、使用其他廠商的盒子。能不能建立一個框架,把加盟店和創(chuàng)建披薩捆綁在一起的同時(shí)又保持一定的彈性?
給披薩店使用的框架
有個做法可讓披薩制作活動局限于PizzaStore類,而同時(shí)又能讓這些加盟店依然可以自由地制作該區(qū)域的風(fēng)味。所要做的事情,就是把createPizza()方法放回到PizzaStore中,不過要把它設(shè)置成“抽象方法”,然后為每個區(qū)域風(fēng)味創(chuàng)建一個PizzaStore的子類。首先,看PizzaStore所做的改變:
public abstract class PizzaStore { public Pizza orderPizza(String type) { Pizza pizza; // 現(xiàn)在createPizza()方法從工廠對象中移回PizzaStore pizza = createPizza(type); // 這些都沒變 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } // 現(xiàn)在把工廠對象移到這個方法中 // 在PizzaStore里,“工廠方法”現(xiàn)在是抽象的 abstract Pizza createPizza(String type);}現(xiàn)在已經(jīng)有一個PizzaStore作為超類;讓每個域類型(NYPizzaStore、ChicagoPizzaStore、CaliforniaPizzaStore)都繼承這個PizzaStore,每個子類各自決定如何制作披薩。允許子類做決定
PizzaStore已經(jīng)有一個不錯的訂單系統(tǒng),由orderPizza()方法負(fù)責(zé)處理訂單,而你希望所有加盟店對于訂單的處理都能一致。各個區(qū)域披薩店之間的差異在于他們制作披薩的風(fēng)味(紐約披薩的薄脆、芝加哥披薩的餅厚等),我們現(xiàn)在要讓現(xiàn)在createPizza()能夠應(yīng)對這些變化來負(fù)責(zé)創(chuàng)建正確種類的披薩。做法是讓PizzaStore的各個子類負(fù)責(zé)定義自己的createPizza()方法。所以我們會得到一些PizzaStore具體的子類,每個子類都有自己的披薩變體,而仍然適合PizzaStore框架,并使用調(diào)試好的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);} // 如果加盟店為顧客提供紐約風(fēng)味的披薩,就使用NyStylePizzaStore,// 因?yàn)榇祟惖腸reatePizza()方法會建立紐約風(fēng)味的披薩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()實(shí)現(xiàn)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; }}現(xiàn)在問題來了,PizzaStore的子類終究只是子類,如何能夠做決定?在NyStylePizzaStore類中,并沒有看到任何做決定邏輯的代碼。關(guān)于這個方面,要從PizzaStore的orderPizza()方法觀點(diǎn)來看,此方法在抽象的PizzaStore內(nèi)定義,但是只在子類中實(shí)現(xiàn)具體類型。orderPizza()方法對對象做了許多事情(例如:準(zhǔn)備、烘烤、切片、裝盒),但由于Pizza對象是抽象的,orderPizza()并不知道哪些實(shí)際的具體類參與進(jìn)來了。換句話說,這就是解耦(decouple)!當(dāng)orderPizza()調(diào)用createPizza()時(shí),某個披薩店子類將負(fù)責(zé)創(chuàng)建披薩。做哪一種披薩呢?當(dāng)然是由具體的披薩店決定。那么,子類是實(shí)時(shí)做出這樣的決定嗎?不是,但從orderPizza()的角度看,如果選擇在NyStylePizzaStore訂購披薩,就是由這個子類(NyStylePizzaStore)決定。嚴(yán)格來說,并非由這個子類實(shí)際做“決定”,而是由“顧客”決定哪一家風(fēng)味的披薩店才決定了披薩的風(fēng)味。工廠方法模式
工廠方法模式定義了一個創(chuàng)建對象的接口,但由子類決定要實(shí)例化的類是哪一個。工廠方法讓類把實(shí)例化推遲到了子類。工廠方法模式(Factory Method Pattern)通過讓子類決定該創(chuàng)建的對象是什么,來達(dá)到將對象創(chuàng)建的過程封裝的目的。PizzaStore就是創(chuàng)建者(Creator)類。它定義了一個抽象的工廠方法,讓子類實(shí)現(xiàn)此方法制造產(chǎn)品。創(chuàng)建者通常會包含依賴于抽象產(chǎn)品的代碼,而這些抽象產(chǎn)品由子類制造。創(chuàng)建者不需要真的知道在制造哪種具體產(chǎn)品。能夠產(chǎn)生產(chǎn)品的類稱為具體創(chuàng)建者。NYPizzaStore和ChicagoPizzaStore就是具體創(chuàng)建者。Pizza是產(chǎn)品類。工廠生產(chǎn)產(chǎn)品,對PizzaStore來說,產(chǎn)品就是Pizza。抽象的Creator提供了一個創(chuàng)建對象的方法的接口,也稱為“工廠方法”。在抽象的Creator中,任何其他實(shí)現(xiàn)的方法,都可能使用到這個工廠方法所制造出來的產(chǎn)品,但只有子類真正實(shí)現(xiàn)這個工廠方法并創(chuàng)建產(chǎn)品。
// Creator是一個類,它實(shí)現(xiàn)了所有操縱產(chǎn)品的方法,但不實(shí)現(xiàn)工廠方法public abstract class Creator{ void anOperation(){ // ... } // Creator的所有子類都必須實(shí)現(xiàn)這個抽象的factoryMethod()方法 abstract void factoryMethod();} // 具體的創(chuàng)建者public class ConcreteCreator extends Creator{ // ConcreteCreator實(shí)現(xiàn)了factoryMethod(),以實(shí)際制造出產(chǎn)品。 @Override void factoryMethod() { // ... }} // 所有產(chǎn)品必須實(shí)現(xiàn)這個接口,這樣一來,// 使用這些產(chǎn)品的類就可以引用這個接口,而不是具體的類public abstract class Product{ void operation(){ // ... }} // 具體的產(chǎn)品public class ConcreteProduct extends Product{}依賴倒置原則
假設(shè)你從未聽說過OO工廠。下面是一個不使用工廠模式的披薩店版本。數(shù)一數(shù),這個類所依賴的具體披薩對象有幾種。
public class DependentPizzaStore { public Pizza createPizza(String style, String type) { Pizza pizza = null; if(style.equals("NY")){ // 處理所有紐約風(fēng)味的披薩 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")){ // 處理所有芝加哥風(fēng)味的披薩 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依賴于所有的披薩對象,因?yàn)樗苯觿?chuàng)建這些披薩對象。如果這些類的實(shí)現(xiàn)改變了,那么可能必須修改PizzaStore。每新增一個披薩類型,就等于讓PizzaStore多了一個依賴。因?yàn)閷τ谂_具體實(shí)現(xiàn)的任何改變都會影響到PizzaStore,我們說PizzaStore“依賴于”披薩的實(shí)現(xiàn)。很清楚的,代碼里減少對于具體類的依賴是件好事。有一個OO設(shè)計(jì)原則就正式闡明了這一點(diǎn):
依賴倒置原則:要依賴抽象,不要依賴具體類。
這個原則說明了:不能讓高層組件依賴低層組件,而且,不管高層或低層組件,兩者都應(yīng)該依賴于抽象。比如,這個例子里的PizzaStore是高層組件,而披薩實(shí)現(xiàn)是低層組件,很清楚的,PizzaStore依賴這些具體披薩類。現(xiàn)在,這個原則告訴我們,應(yīng)該重寫代碼以便于我們依賴抽象類,而不依賴具體類。對于高層及低層模塊都應(yīng)該如此。
依賴倒置原則的應(yīng)用
非常依賴披薩店的主要問題在于:它依賴每個披薩類型。因?yàn)樗窃谧约旱膐rderPizza()方法中,實(shí)例化這些具體類型的。如何在orderPizza()方法中,將這些實(shí)例化對象的代碼獨(dú)立出來?我們知道,工廠方法剛好能派上用場。所以,應(yīng)用工廠方法后,類圖看起來就像這樣:

PizzaStore現(xiàn)在依賴Pizza這個抽象類。具體披薩類也依賴Pizza抽象,因?yàn)樗鼈儗?shí)現(xiàn)了Pizza接口。在應(yīng)用工廠方法后,高層組件(也就是PizzaStore)和低層組件(也就是這些披薩)都依賴了Pizza抽象。想要遵循依賴倒置原則,工廠方法并非是唯一的技巧,但卻是最有威力的技巧之一。
遵循依賴倒置原則的指導(dǎo)方針
下面的指導(dǎo)方針,能幫你避免在OO設(shè)計(jì)中違反依賴倒置原則:
變量不可以持有具體類的引用
如果使用new,就會持有具體類的引用。你可以改用工廠來避開這樣的做法。
不要讓類派生自具體類
如果派生自具體類,你就會依賴具體類。請派生自一個抽象(接口或抽象類)。
不要覆蓋基類中已實(shí)現(xiàn)的方法
如果覆蓋基類已實(shí)現(xiàn)的方法,那么你的基類就不是一個真正適合被繼承的抽象。基類中已實(shí)現(xiàn)的方法,應(yīng)該由所有的子類共享。要完全遵守這些指導(dǎo)方針似乎不太可能,但是如果你深入體驗(yàn)這些方針,將這些方針內(nèi)化成你思考的一部分,那么在設(shè)計(jì)時(shí),你將知道何時(shí)有足夠的理由違反這樣的原則。比方說,如果有一個不像是會改變的類,那么在代碼中直接實(shí)例化具體類也就沒什么大礙。另一方面,如果有個類可能改變,你可以采用一些好技巧(例如工廠方法)來封裝改變。
原文出處: cashow
新聞熱點(diǎn)
疑難解答