国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

《Head first設(shè)計(jì)模式》學(xué)習(xí)筆記 – 工廠方法模式

2019-11-08 00:24:38
字體:
供稿:網(wǎng)友

工廠方法模式定義了一個創(chuàng)建對象的接口,但由子類決定要實(shí)例化的類是哪一個。工廠方法讓類把實(shí)例化推遲到了子類。

預(yù)定披薩

假設(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)用工廠方法后,類圖看起來就像這樣:

factory_pattern_dependency_2

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


發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 金堂县| 眉山市| 平谷区| 定襄县| 涡阳县| 临沂市| 全州县| 海淀区| 莫力| 台南县| 买车| 阿克陶县| 台州市| 乌兰察布市| 天气| 舒城县| 璧山县| 垦利县| 南皮县| 北京市| 南和县| 虹口区| 天峨县| 阿坝| 巴林左旗| 新兴县| 长葛市| 咸宁市| 乐安县| 镇原县| 察雅县| 周口市| 苍南县| 永仁县| 广河县| 乌拉特前旗| 平南县| 洛隆县| 鄢陵县| 无极县| 天祝|