1. 程式人生 > >Head First Design Mode(5)-工廠模式

Head First Design Mode(5)-工廠模式

工廠模式:    

    烘烤OO的精華——烘烤某些鬆耦合的OO設計;

    除了使用new操作符之外,還有更多製造物件的方法;

    本章我們將瞭解到例項化的這個活動不應該總是公開的進行,認識到初始化經常造成“耦合”問題,並瞭解工廠模式如何從複雜的依賴中幫你脫困;

new——具體:

    每次使用new的時候都是在針對實現程式設計,因為它例項化了一個具體 的類;

    我們知道程式碼繫結具體類會導致程式碼更脆弱,更缺乏彈性;

    我們可能會根據if條件判斷類在執行時具體需要例項化哪個子類;

new的問題:

    new並沒有問題,問題在於“改變”影像了new的使用;

    在為一個父類變數賦值時,所有例項化子類的情形會根據不同的條件進行,這就是一種改變,直到執行時你才會直到具體例項化的是哪個子類;

    而這種改變會造成耦合,比如條件的修改,新增子類的例項化等,這些都需要修改程式碼;

我們的程式碼沒有“對修改關閉”,用新的具體來擴充套件程式碼,必須重新開啟它;

我們的設計原則:

    對擴充套件開放 對修改關閉;

    那我們先將變化分類出來:將例項化具體類的程式碼從應用中抽離,或者封裝,使他們不會干擾應用的其他部分;

我們以比薩店的場景進行舉例:

    你有一家 比薩店,顧客可以對比薩進行下單;

我們編碼如下:

現在需要更多的比薩型別,我們將orderPizza方法加上一個型別的引數:String type;

    進而在orderPizza方法中通過type.equals(“sometype")進行if條件判斷,例項化不同的Pizza子型別,賦值給Pizza pizza變數;

    這裡可以將Pizza定義為一個介面,所有的Pizza子型別都需實現該介面;

    在例項化具體的pizza之後,我們繼續進行比薩的準備-烘烤-切片-裝盒!

這種設計的問題 在更多的比薩型別新增 或 減少時就會提現出來:

    隨著比薩選單的修改,orderPizza方法中的這樣一段if條件下多個例項化,必須一再修改;

    這將導致,無法讓orderPizza()對修改關閉;

我們可已經這段變化的程式碼移到一個專門的類中,這個類只管建立Pizza,我們將這個類的物件稱之為“工廠”;

工廠(factory):

    處理建立物件的細節,如果有一個SimplePizzaFactory,那麼orderPizza()就變成此物件的客戶;

    當客戶下單,pizzaStore呼叫orderPizza()的時候,就叫比薩工廠做一個,orderPizza()只關心從工廠得到一個比薩;

    這個比薩(類)實現了Pizza介面,可以呼叫prepare bake cut box進行對應的操作;

提前熟悉下,我們會學習到三種工廠相關的模式:

    簡單工廠模式(更像一種程式設計習慣);

    工廠方法模式;

    抽象工廠模式;

工廠的好處:

    一個工廠可以有很多的客戶,工廠封裝了根據條件建立物件這一“變化”的過程,當需要變更時,修改此類即可;

簡單工廠模式——:

現在我們根據上述思路重新實現PizzaStore類:

    我們把new操作符替換成工廠物件的建立方法;

編譯執行:

bogon:簡單工廠模式(更像一種程式設計習慣) huaqiang$ java SimplePizzaDriver

PizzaSub1 init!

PizzaSub1 prepare!

PizzaSub1 bake!

PizzaSub1 cut!

PizzaSub1 box!

定義簡單工廠:

    上述編碼示例,對應的就是簡單工廠,相比於稱之為設計模式,更像一種程式設計習慣,由於使用的比較多,我們稱之為 簡單工廠模式;

    其中的SimplePizzaFactory的createPizza()方法也通常宣告為靜態;

    工廠生產的產品定義為具體類,同一實現了相同的介面,被工廠建立之後返回給客戶;    

    “實現一個介面”泛指“實現某個超型別”(可以是類或介面);

類圖如下:

經過簡單工廠模式的熱身,我們繼續介紹兩個重量級的模式,他們都是工廠;

工廠方法模式——:

加盟比薩店:

    加盟店可以利用我們已有比薩店的程式碼,讓比薩的流程一致,畢竟我們是連鎖店;

        一家加盟店能製造北方口味的比薩:厚餅 重味道的醬料 大量的芝士;

        一家加盟店能製造南方口味的比薩:薄餅 輕味道的醬料 少量的芝士;

為了讓加盟店和比薩的建立繫結在一起實現一定的質量控制,又想讓比薩的口味設定保持一定的彈性,我們需要“框架”:

    目標:

        讓比薩製作活動侷限於PizzaStore類,同時又能讓這些加盟店依然可以自由地製作該區域的風味;

    方案:

        把createPizza()方法放回到PizzaStore中,不過要把它設定為“抽象方法”,然後為每一個區域建立一個PizzaStore的子類;

我們編碼如下:

編譯執行:

bogon:工廠方法模式 huaqiang$ javac *.java

bogon:工廠方法模式 huaqiang$ java FactoryMethodDriver

PizzaSub1 init!

PizzaSub1 prepare!

PizzaSub1 bake!

PizzaSub1 cut!

PizzaSub1 box!

我們用了一個PizzaStore的超類,每個區域的比薩店都繼承這個PizzaStore,每個子類各自決定如何製造比薩;

    “不變的”內容,統一定在了抽象超類中;

    “變的”內容,則由子類覆蓋父類方法;

各個區域的比薩店之間的差異在於他們製作比薩的風味,現在要讓createPizza()能夠應對這些變化來負責建立正確的種類的比薩——讓PizzaStore的各個子類負責定義自己的createPizza()方法;我們得到了一些PizzaStore具體的子類,每個子類都有自己的製作比薩的變體,而仍然適合PizzaStore框架,並使用除錯好的orderPizza()方法;

解耦:

    orderPizza()方法對Pizza物件做了許多事情(準備 烘烤 切片 裝盒);但由於Pizza物件是抽象的,orderPizza()並不知道那些實際的具體類參與進來,這就是解耦;

    相應的pizza也是呼叫createPizza()取得的,具體做的是哪一種比薩,由具體的比薩店來決定;

新開一家比薩店:

    繼承PizzaStore,然後提供createPizza()方法實現自己的比薩風味即可;

    超類的orderPizza()方法,並不知道正在建立的比薩是哪一種,他只知道這個比薩可以被準備、烘烤、切片、裝盒;

宣告一個工廠方法:

    我們再仔細看下加盟比薩店的示例;

    相比於簡單工廠模式由一個物件負責所有具體類的例項化,現在通過對PizzaStore做的一些修改,變成由一群子類來負責例項化;

    例項化比薩的責任被移到了一個“方法”中,此方法就如同是一個“工廠”;

工廠方法用來處理物件的建立,並將這樣的行為封裝在子類中,這樣,客戶程式中關於超類的程式碼就和子類物件建立程式碼解耦了;

    abstract Product factoryMethod(String type);

        工廠方法是抽象的,所以用子類來處理物件的建立;

        工廠方法必須返回一個產品,超類中的其他方法,通常用到工廠方法的返回值;

        工廠方法將客戶(超類中)和實際建立產品的程式碼分隔開來;

不要忽略“比薩”本身:

    Pizza可以是一個抽象超類,也可以定義為介面;

    我們具體的比薩類,擴充套件自Pizza;

比薩加盟店的例項 和 我們上述的說明 對應的就是 工廠方法模式 的一個應用場景和說明;

認識工廠方法模式:

    所有的工廠模式都是用來封裝物件的建立;

    工廠方法模式(Factory Method Pattern)通過讓子類決定該建立的物件是什麼,來達到將物件的建立過程封裝的目的;

類圖:

    建立者(Creater)類:

        建立者通常會包含依賴於抽象產品的程式碼,而這些抽象產品由子類製造;

        建立者不需要真的知道在製造那種具體產品;

    產品(Product)類:

建立者和產品:

    將一個orderPizza()方法和一個工廠方法聯合起來,就可以成為一個框架;

    此外,工廠方法將生產知識封裝進哥哥建立者,這種做法也可以被視為一個框架;

建立者和產品類層級是平行的:

    它們都有各自的抽象類,各自的抽象類 還有許多具體的子類,每個子類都有自己特定的實現;

        建立者的子類封裝建立產品子類的知識;

        建立者的抽象類關聯使用產品抽象類的知識;

定義工廠方法模式:

    正式定義:

        工廠方法模式定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個;工廠方法讓類把例項化推遲到子類;

    工廠方法模式能夠封裝具體型別的例項化;

        抽象的Creater提供了一個建立物件的方法的介面,成為“工廠方法”;在抽象的Creater中,任何其他市縣的方法,都可以使用到這個工廠方法所制

造的產品,但只有子類真正實現這個工廠方法並建立產品(選擇類子類,自然就決定了實際建立的產品);

工廠方法模式類圖:

優點:

    將產品的實現從使用中解耦,增加改變產品的實現,Creater並不會受到影響,因為Creater和任何的ConcreteProduct之間不是緊耦合;

    工廠方法不一定是抽象的,我們也可以定義一個預設的工廠方法來產生某些具體的產品;

    和簡單工廠模式相比,簡單工廠的做法可已經物件的建立封裝起來,但是簡單工廠不具備工廠方法的彈性,因為簡單工廠不能變更正在建立的產品;

    將建立物件的程式碼集中在一個物件或方法中,可以避免程式碼中的重複,方便維護;客戶例項化物件只會依賴介面,而不是具體的類;幫助我們針對介面程式設計,而不針對實現介面,讓程式碼更具有彈性,方便今後擴充套件;

讓我們假設下,如果不使用工廠模式的類設計會如何?

    PizzaStore_1->PizzaStoreSub_N->PizzaSub_N;

    一個比薩店類會依賴多個具體的比薩店,每個比薩店 又要依賴 具體的比薩型別,這就導致 如果一個比薩的實現改變了,最終可能會影響到PizzaStore;

    這種依賴並不是我們想要的,我們有一個OO原則,來闡明這一點;

依賴導致原則(Dependency Inversion Principle):

    要依賴抽象,不要依賴具體類;

此原則說明了:不要讓高層元件依賴底層元件,而且,不管高層或底層元件,“兩者”都應該依賴於抽象;

    高層元件:是由其他底層元件定義其行為的類;PizzaStore就是一個高層元件,因為他的行為是由比薩定義的;

工廠方法模式就是對這一原則的應用:

    PizzaStore依賴每個Pizza型別,雖然已經抽象了一個Pizza,但是PizzaStore還是依賴了具體的Pizza類,應為在其orderPizza()方法中,更具條件例項化了具體的Pizza型別,所以這個抽象並沒有什麼影響力;

    而工廠方法剛好將例項化程式碼的程式碼獨立了出來;

    遵循依賴倒置原則,工廠方法不是唯一的,確實最有效的;

遵循依賴倒置原則的指導方針:

    1)變數不可以持有具體類的引用;(使用new就會持有具體類的引用)

    2)不要讓類派生自具體類;(派生自具體類就會依賴具體類,所以請派生一個抽象(介面或抽象類))

    3)不要覆蓋基類中已實現的方法;(基類已實現的方法應該由所有子類共享,否則就不是一個合適的抽象)

抽象工廠模式——:

再看比薩店——如何確保原料的一致性

    比薩的加盟店要求使用相同的配料,以保證品質,但是不同的比薩店可能會從各自所在地區進行原材料的採集;

    顯然,南方的醬料 不同於 北方的醬料,其他的材料也不盡相同;

原料家族會不斷壯大,雖然每個加盟店的比薩配料名稱一致,但是不同的比薩店的配料都會有各自品種的特點;

所有對鄉村的比薩都是使用相同的元件製成的,但是每個區域對於這些元件卻有不同的實現;

建造原料工廠:

    我們建一個工廠來生產原料,負責建立家族中每一種原料,比如生產醬料和麵團;

    我們先為工廠定義一個介面:PizzaIngredientFactory;其中定義了各種建立原料的方法;

        那麼,為每一個區域建立一個工廠,就可以建立繼承/實現PizzaIngredientFactory的子類/具體類;

        這些類,也可以在合適的區域間共享(PizzaIngredientFactory需要是一個抽象類);

    新的方案就是,將新的原料工廠整合進舊的PizzaStore程式碼中;

        建立PizzaStoreSub1所在區域的原料工廠-PizzaIngredientFactorySub1;

        現在原料和 原料工廠 都已經準備好了,注意原料也可以抽象為原料介面,這裡的場景名不復雜就不繼續抽象了;

重做比薩:

    通過原料工廠實現Pizza和原料之間的解耦;    

    把工廠傳遞給每一個比薩,以便比薩能從工廠中取得原料;

我們發現:

    上述類圖中既有我們已經學習的工廠方法模式,也有我們即將介紹的抽象工廠模式;

    二者兩種模式,其實都是從簡單工廠模式演化而來的:

        簡單工廠模式中的Factory類,如果移到了客戶類成為一個抽象方法,有其子類提供建立,對應的就是 工廠方法模式;

        簡單工廠模式中的Factory類,如果抽象為一個介面,由其子類提供建立原料家族的實現,對應的就是 抽象工廠模式;

我們繼續往下看:

    我們引入新型別的工廠,也就是所謂的抽象工廠,來建立比薩原料家族;

    通過抽象工廠所提供的介面,可以建立產品的家族,利用這個介面編碼,程式碼實現實際工廠解耦;

定義抽象工廠模式:

    抽象工廠模式提供了一個介面,用於建立相關或依賴物件的家族,而不需要明確指定具體類;

就好像子類的prepare()中所做的(圖中方法錯誤 應去掉 abstract字首);

類圖:

    客戶需要ProductA1和ProductB1,只需要使用一個具體的工廠取建立即可,而不需要例項化任何產品物件;

可以看到:

    抽象工廠定義了一個負責建立一組產品的介面,抽象工廠的子類提供具體的方法,其中使用了多個工廠方法;

    當需要建立產品家族和想讓製造的相關產品集合起來時,可以使用抽象工廠;

    工廠方法則可以把客戶程式碼從需要例項化的具體類中解耦,如果還不知道需要例項化哪些具體的類可使用,繼承為子類然後實現工廠方法即可;

總結:

1.所有的工廠都是用來封裝物件的建立;

2.簡單工廠,雖然不是真正的設計模式,但仍不失為一個簡單方法,可以將客戶程式從具體類中解耦;

3.工廠方法使用繼承:把物件的建立委託給子類,子類實現工廠方法來建立物件;

4.抽象工廠使用物件組合:物件的建立被實現在工廠介面所暴露出來的方法中;

5.所有工廠模式都通過減少應用程式和具體類之間的依賴促進鬆耦合;

6.工廠方法允許類將例項化延遲到子類進行;

7.抽象工廠建立相關的物件家族,而不需要依賴它們的具體類;

8.依賴倒置原則,指導我們避免依賴具體型別,而儘量依賴抽象;

9.工廠很厲害,幫助我們更好的針對抽象程式設計,而不是針對具體類程式設計;

OO基礎:

    抽象;

    封裝

    繼承;

    多型;

OO原則:

    封裝變化

    多用組合,少用繼承

    針對介面程式設計,不針對實現程式設計

    為互動物件之間的鬆耦合設計而努力;

    類應該對擴充套件開放,對修改關閉;

    ——依賴抽象,不要依賴具體類;

OO模式:

    策略模式:定義演算法族,分別封裝起來,讓他們之間互相替換,此模式讓演算法的變化獨立於使用演算法的客戶;

    觀察者模式:在物件之間定義一對多的依賴,這樣一來,當一個物件改變狀態,依賴它的物件都會收到通知,並自動更新;

    裝飾者模式:動態地將責任附加到物件上;想要擴充套件功能,裝飾者提供有別於繼承的另一種選擇;

    ——簡單工廠模式;

    ——工廠方法模式:定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個;工廠方法讓類把例項化推遲到子類;

    ——抽象工廠模式:提供一個介面,用於建立相關或依賴物件的家族,而不需要明確具體的類;