《Head First设计模式》第四章笔记 工厂模式
之前我們一直在使用new操作符,但是實例化這種行為并不應該總是公開的進行,而且初始化經(jīng)常會造成耦合問題,工廠模式將擺脫這種復雜的依賴,本次內(nèi)容包括簡單工廠,工廠方法和抽象工廠三種情況。
| 1 2 3 4 5 6 | Duck duck; if(a){ ????duck=new Duck1(); }else{ ????duck=new Duck2(); } |
如上面代碼中使用new實例化一個類時,使用的是實現(xiàn),而不是接口,代碼捆綁著具體類會導致代碼更脆弱缺乏彈性,后續(xù)的維護、修改等操作容易出錯。
使用new操作符會造成的問題:
1.如果你針對接口編程,你可以利用多態(tài),實現(xiàn)該接口并沒有太大問題
2.但是如果代碼中使用了很多具體類,一旦加入新的類,就必須改變代碼,這就違反了開放-關(guān)閉原則。
項目舉例
假設你有一家 pizza 店,你有很多種 pizza,要在系統(tǒng)中顯示你所有pizza種類。
實現(xiàn)這個功能并不難,
使用普通方式實現(xiàn):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; ????} } |
但是如果新上市一種pizza或者下架一種pizza,你就需要修改這段代碼,這樣就沒有做到對修改關(guān)閉。
簡單工廠:
于是我們使用之前學到的知識,找到變化的代碼和不變的代碼,進行封裝了:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class SimpleFactory { ????public Pizza createPizza(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(); ????????} ????????return pizza; ????} } ? public class PizzaStore { ????SimpleFactory simpleFactory; ????public PizzaStore(SimpleFactory simpleFactory) { ????????this.simpleFactory = simpleFactory; ????} ????Pizza orderPizza(String type) { ????????Pizza pizza =?null; ????????pizza = simpleFactory.createPizza(type); ????????pizza.prepare(); ????????pizza.bake(); ????????pizza.cut(); ????????pizza.box(); ????????return pizza; ????} } |
這么做的好處看起來好像只是把原來的代碼放進到另外一個類中,但是當以后出現(xiàn)變化時,自需要修改這個類,就可以,并且這樣做的好處還在于可能現(xiàn)在只有客人點餐才會用到這段代碼,如果可以點外賣的話,仍然可以用這段代碼,代碼得到了復用
上面的簡單工廠并不是一個真正的模式,只是一種編程習慣,這個不能算工廠模式,不過也算是基本滿足需求。
工廠方法模式:
背景更新:假如現(xiàn)在你要開分店,各種加盟商進來后,他們都要開發(fā)符合本地口味的pizza,那就需要各個地方都有一個工廠,也就是每個地方繼承SimpleFactory類,但是每個工廠并不是完全使用你原來的烘培方法。
工廠方法模式類圖如下:
對應代碼·:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public abstract class PizzaStore { ????Pizza orderPizza(String type) { ????????Pizza pizza =?null; ????????pizza = createPizza(type); ????????pizza.prepare(); ????????pizza.bake(); ????????pizza.cut(); ????????pizza.box(); ????????return pizza; ????} ????protected abstract Pizza createPizza(String type); } ? 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; ????} } |
現(xiàn)在訂購這些訂單時,自需要實例化各自風格的工廠,就算都是芝士披薩,也是來自不同商店口味的。
?
?
總結(jié):
- 工廠方法模式定義了一個創(chuàng)建對象的接口,但由子類決定要實例化的是哪一個,工廠方法讓類把實例化推遲到子類,工廠方法模式的優(yōu)點在于幫助產(chǎn)品從使用中分離,從使用中解耦。
- 和簡單工廠的區(qū)別,兩者類似,但是在于createPizza方法,工廠方法讓每個上篇自行負責,而簡單工廠使用的則是PizzaStore對象。
- 簡單工廠把所有的事情在一個地方干完了,然而工廠方法則是寫了一個框架,讓子類具體實現(xiàn),PizzaStore作為工廠的orderPizza方法提供了一個一般的框架,以便創(chuàng)建披薩,其具體依賴工廠的方法來創(chuàng)建具體的披薩。
- 兩者都實現(xiàn)了不讓創(chuàng)建對象的代碼到處亂跑。
看看對象依賴情況,原來的PizzaStore依賴于所有的Pizza對象,當這些對象發(fā)生改變時,可能會影響到PizzaStore,PizzaStore時依賴具體類的,PizzaStore就是高層組件,Pizza各種對象就是低層組件,但是設計原則有一條是:依賴抽象,不要依賴具體類
而使用工廠方法之后就不在出現(xiàn)這種依賴很多具體類的情況了。
?
現(xiàn)在背景有變,有些加盟店,使用低價原料來增加利潤,你必須采取一些手段,以免毀掉你的披薩店品牌。
你打算建造一家成產(chǎn)原料的工廠,并將原料運送到各家加盟店,那么剩下最后一個問題,不同的區(qū)域原料是不一樣的,對于兩個加盟店給出了兩組不同的原料。
?
類圖:
?
建造原料工廠
我們要建造一個工廠來生產(chǎn)原料,這個工廠負責創(chuàng)建原料家族中的每一種原料。
| 1 2 3 4 5 6 7 8 | public interface PizzaIngredientFactory { ????public Dough createDough(); ????public Sauce createSauce(); ????public Cheese createCheese(); ????public Veggies[] createVeggies(); ????public Pepperoni createPepperoni(); ????public Clams createClams(); } |
要做的事情是:
1.為每個區(qū)域建造一個工廠,你需要創(chuàng)建一個繼承自PizzaIngredientFactory的子類來實現(xiàn)每一個創(chuàng)建方法。
2.實現(xiàn)一組原料類供工廠使用,例如RegianoCheese,RedPeppers,ThickCrustDough.這些類可以在何時的區(qū)域間共享。
3.然后你仍然需要將這一切組織起來,將新的原料工廠整合進舊的PizzaStore代碼中。
創(chuàng)建紐約的原料工廠
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class NYPizzaIngredientFactory?implements PizzaIngredientFactory{ ????@Override ????public Dough createDough() { ??? ? ??return null; ????} ????@Override ????public Sauce createSauce() { ??? ? ??return null; ????} ????@Override ????public Cheese createCheese() { ??? ? ??return null; ????} ????@Override ????public Veggies[] createVeggies() { ??? ? ??return new Veggies[0]; ????} ????@Override ????public Pepperoni createPepperoni() { ??? ? ??return null; ????} ????@Override ????public Clams createClams() { ??? ? ??return null; ????} } |
重做披薩
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | abstract class Pizza{ ????String name; ????Dough dough; ????Sauce sauce; ????Veggies veggies[]; ????Cheese cheese; ????Pepperoni pepperoni; ????Clams clams; ????abstract void prepare(); ????void cut(){ ??? ? ??System.out.println("Cutting the pizza into diagonal slices"); ????} ????void box(){ ??? ? ??System.out.println("Place pizza in official PizzaStore box"); ????} ????void setName(String name){ ??? ? ??this.name=name; ????} ????public String getName(){ ??? ? ??return name; ????} ????public void toString2(){ ??? ? ??//這里打印披薩的代碼 ????} } |
繼續(xù)重做披薩
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class CheesePizza?extends Pizza { ????//這里組合了一個PizzaIngredientFactory對象的引用,用于提供不同的原料 ????PizzaIngredientFactory ingredientFactory; ?? ????/** ??? ?* 通過傳入一個PizzaIngredientFactory原料工廠,我們可以在制作Pizza的時候動態(tài)的產(chǎn)生所需要的原料 ??? ?* @param ingredientFactory ??? ?*/ ????? ????public CheesePizza(PizzaIngredientFactory ingredientFactory) { ??? ? ??this.ingredientFactory = ingredientFactory; ????} ?? ????void prepare() { ??? ? ??System.out.println("Preparing " + name); ??? ? ??dough = ingredientFactory.createDough(); ??? ? ??sauce = ingredientFactory.createSauce(); ??? ? ??cheese = ingredientFactory.createCheese(); ????} } |
再回到披薩店
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class NYPizzaStore?extends PizzaStore { ?? ????protected Pizza createPizza(String item) { ??? ? ??Pizza pizza =?null; ??? ? ??PizzaIngredientFactory ingredientFactory =?new NYPizzaIngredientFactory(); ?? ??? ? ??if (item.equals("cheese")) { ??? ??? ? ? ? ??pizza =?new CheesePizza(ingredientFactory); ??? ? ? ? ??pizza.setName("New York Style Cheese Pizza"); ??? ??? ? ??}?else if (item.equals("veggie")) { ?? ??? ? ? ? ??pizza =?new VeggiePizza(ingredientFactory); ??? ? ? ? ??pizza.setName("New York Style Veggie Pizza"); ?? ??? ? ??}?else if (item.equals("clam")) { ?? ??? ? ? ? ??pizza =?new ClamPizza(ingredientFactory); ??? ? ? ? ??pizza.setName("New York Style Clam Pizza"); ?? ??? ? ??}?else if (item.equals("pepperoni")) { ??? ? ? ? ??pizza =?new PepperoniPizza(ingredientFactory); ??? ? ? ? ??pizza.setName("New York Style Pepperoni Pizza"); ?? ??? ? ??}? ??? ? ??return pizza; ????} } |
我們做了些什么?
我們引入新類型的工廠,也就是所謂的抽象工廠,來創(chuàng)建披薩原料家族。
通過抽象工廠所提供的接口可以創(chuàng)建產(chǎn)品的家族,利用這個接口書寫代碼,我們的代碼將從實際工廠解耦,以便在不同上下文中實現(xiàn)各式各樣的工廠,制造出各種不同的產(chǎn)品。
定義抽象工廠模式
抽象工廠模式提供一個接口,用于創(chuàng)建相關(guān)或依賴對象的家族,而不需要明確指定具體類。
?
工廠方法與抽象工廠
抽象工廠的每個方法都實際上看起來像工廠方法,方法聲明成抽象,子類方法去覆蓋這些方法去創(chuàng)建對象。
抽象工廠的任務是定義一個負責創(chuàng)造一組產(chǎn)品的接口,每個方法就是創(chuàng)建一種產(chǎn)品,這就是工廠方法,所以在抽象工廠中利用工廠方法來實現(xiàn)生產(chǎn)方法是自然的。
對比:
- 兩者都是用來創(chuàng)建對象,但是工廠方法使用的是繼承,而抽象工廠使用的是對象的組合。
- 比如工廠方法創(chuàng)建pizza是各種PizzaStore繼承后覆蓋createPizza()方法實現(xiàn)的,讓客戶從具體類型中解耦。
- 但是對于抽象工廠,提供了一個用來創(chuàng)建一個產(chǎn)品家族的抽象類型,這個類型的子類定義了產(chǎn)品被產(chǎn)生的方法,要想使用這個工廠必須實例化它,然后將它傳入一些針對抽象類型所寫的代碼中。
- 比如PizzaIngredientFactory這個接口定義了一堆原料的做法,而對于紐約的原料加工廠先實現(xiàn)這些接口,然后紐約的商店實例化這個原料加工廠,就保證了不同的商店使用到各自不同的原料,把客戶從所使用的實際產(chǎn)品中解耦。
- 一群還是一個,一群產(chǎn)品集合用抽象工廠,具體哪些類中可以確定一個抽象類,子類繼承實現(xiàn)使用工廠方法
?
?
設計原則:依賴倒置
依賴倒置原則:要依賴抽象,不要依賴具體類。高層次的模塊不應該依賴于低層次的模塊,他們都應該依賴于抽象。抽象不應該依賴于具體實現(xiàn),具體實現(xiàn)應該依賴于抽象。
用依賴倒置原則重構(gòu)代碼
下面是一些指導方針來避免違反依賴倒置原則:
- 變量不可以持有具體類的引用。如果使用new,就會持有具體類的引用,你可以改成工廠來避免
- 不要讓類派生自具體類,如果派生自具體類,你就會依賴具體類,請派生自抽象(接口或者抽象類)
- 不要覆蓋基類中已經(jīng)實現(xiàn)的方法,如果覆蓋,那么基類說明就不是一個真正適合被繼承的抽象,基類中已經(jīng)實現(xiàn)的方法,應該有所有的子類共享
要盡量達到這些原則,但不是隨時都要遵守
?
總結(jié)
工廠:封裝對象的創(chuàng)建,處理創(chuàng)建對象的細節(jié)
靜態(tài)工廠:利用靜態(tài)方法定義一個簡單的工廠。優(yōu)點:不需要創(chuàng)建工廠類的實例化。缺點:不能通過繼承改變創(chuàng)建方法行為。
簡單工廠:簡單工廠并不是一種設計模式,因為只是簡單的把創(chuàng)建對象的代碼封裝起來
工廠模式:在父類定義了一個創(chuàng)建對象的接口,通過讓子類決定創(chuàng)建的對象是什么,來達到讓對象創(chuàng)建的過程封裝的目的。工廠方法讓類把實例化推遲到子類
抽象工廠:提供一個接口,用于創(chuàng)建相關(guān)或依賴對象的家族,而不需要指明具體的類
- 工廠方法用來處理對象的創(chuàng)建,并將這樣的行為封裝在子類中。這樣客戶程序中超類的代碼就和子類對象的創(chuàng)建部分解耦了
- 簡單工廠vs工廠模式:簡單工廠把全部的事情在一個地方做完了,而工廠模式是一個創(chuàng)建框架,讓子類決定如何實現(xiàn)
- 抽象工廠vs工廠模式
- 抽象工廠的方法經(jīng)常以工廠方法的方式實現(xiàn),抽象工廠的任務是定義一個負責創(chuàng)建一組產(chǎn)品的接口
- 工廠方法使用繼承,抽象工廠使用組合
- 工廠方法只是用來創(chuàng)建一種產(chǎn)品,而抽象工廠創(chuàng)建的是一個產(chǎn)品家族
- 使用工廠模式意味著需要擴展一個類并覆蓋它的工廠方法。抽象工廠提供了一個創(chuàng)建產(chǎn)品家族的抽象類型,類型的子類定義了產(chǎn)品生產(chǎn)的方式
總結(jié)
以上是生活随笔為你收集整理的《Head First设计模式》第四章笔记 工厂模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: leetcode 152 乘积最大子序列
- 下一篇: 《Head First设计模式》第六章笔