上章講的是建立型的設計模式,工廠方法(上),此次要講的是另外一本書關於工廠方法的一些概念以及案例、模型等等。就像電影「風雨哈佛路」中那個老師提問,爲何要用另外的一張一張紙質資料,而不直接用書籍。女主回答說,由於不一樣的資料聚集了不一樣人的思想。php
假設你有一個關於我的事務管理的項目,功能之一是管理預定對象(Appointment)。如今要和另外一個公司創建關係,須要一個叫作BloggsCal的格式來和他們交流預定相關的數據。可是你未來可能要面對更多的數據格式設計模式
在接口上能夠當即定義兩個類,app
1.Class ApptEncoder:數據編碼器,將Appointment轉換成一個專有格式
2.Class CommsManager:管理員類,用來獲取數據編碼器,並使用編碼器進行第三方通訊this
使用模型屬於來描述的話,CommsManager就是建立者(Creator),而ApptEncoder是產品(product)
那麼如何獲得一個具體的ApptEncoder對象?編碼
<?php abstract class ApptEncoder{ //產品類 abstract function encode(); } class BloggsApptEncoder extends ApptEncoder{ //實際產品1 function encode(){ return "Appointment data encoded in BloggsCal Format\n"; } } class MegaApptEncoder extends ApptEncoder{ //實際產品2 function encode(){ return "Appointment data encoded in MegaCal Format\n"; } } class CommsManager{ //建立者(管理者) function getApptEncoder(){ return new BloggsApptEncoder(); } } ?>
CommsManager類負責生成BloggsApptEncoder對象,可是當你和合做方關係改變,被要求轉換系統來使用一個新的格式MegaCal時,那麼代碼就須要作另外的改變了spa
class CommsManager{ const BLOGGS = 1; const MEGA = 2; private $mode =1; function __construct($mode){ $this->mode = $mode; } function getApptEncoder(){ switch($this->mode){ case (self::MEGA): return new MegaApptEncoder(); default: return new BloggsApptEncoder(); } } } $comms = new CommsManager(CommsManager::MEGA); $appt = $comms->getApptEncoder(); print $appt->encode();
在類中咱們使用常量標誌定義了腳本可能運行的兩個模式:MEGA和BLOGGS,在getApptEncoder()方法中使用switch語句來檢查$mode屬性,並實例化相關編碼器設計
可是這種方法還有一種小缺陷,一般狀況下,建立對象須要指定條件,可是有時候條件語句會被看成Awful的「Code taste」,由於可能會致使重複的條件語句蔓延在代碼中。咱們知道建立者已經可以提供交流日曆數據的功能,可是若是合做方要求提供頁眉和頁腳來約束每次預定,那該怎麼辦?code
結果是,你須要在上面的代碼中加入新的方法orm
function getHeaderText(){ switch($this->mode){ case (self::MEGA): return "This is Mega format header!\n"; default: return "This is Bloggs format header!\n"; } }
Obviously,這會使得它在getApptEncoder()方法同時使用時,重複地使用了switch判斷,一旦客戶要增長其它需求,那工做量以及冗餘程度會更重對象
總結一下當前須要思考的:
1.在代碼運行時咱們才知道要生成的對象類型(BloggsApptEncoder或者是MegaApptEncoder)
2.咱們須要可以相對輕鬆地加入一些新的產品類型(如新的業務處理方式SyncML)
3.每個產品類型均可定製特定的功能(如上文提到的頁眉頁腳)
另外注意咱們使用的條件語句,其實能夠被多態替代,而工廠方法模式剛好能讓咱們用繼承和多態來封裝具體產品的建立,黃菊花說,咱們要爲每種協議建立CommsManager的每個子類,而每個子類都要實現getApptEncoder方法
工廠方法模式把建立者類與要生產的產品分離開來。建立者是一個工廠類,其中定義了用於生成產品對象的類方法,若是沒有提供默認實現,那麼就由建立者類的子類來執行實例化。通常來講,就是建立者類的每一個子類實例化一個相應產品子類
因此咱們把CommsManager從新指定爲抽象類,這樣就能夠獲得一個靈活的父類,並把全部特定協議相關的代碼放到具體的子類中
下面是簡化過的代碼:
abstract class ApptEncoder{ abstract function encode(); } class BloggsApptEncoder extends ApptEncoder{ function encode(){ return "Appointment data encode in BloggsCal format!\n"; } } abstract class CommsManager{ abstract class getHeaderText(); abstract class getApptEncoder(); abstract class getFooterText(); } class BloggsCommsManager extends CommsManager{ function getHeaderText(){ return "BloggsCal Header"; } function getHeaderText(){ return new BloggsApptEncoder(); } function getFooterText(){ return "BloggsCal Footer"; } }
如今當咱們要求實現MegaCal時,只須要給CommsManager抽象類寫一個新的實現
注意到上面的建立者類與產品的層次結構很類似,這是使用工廠方法模式的常見結果,造成了一種特殊的代碼重複。另外一個問題是該模式可能會致使沒必要要的子類化,若是你爲建立者建立子類的緣由是爲了實現工廠方法模式,那麼最好再考慮一下(這就是爲何在例子中引入頁眉頁腳)
上面例子中咱們只關注了預定功能。
咱們經過加入更多編碼格式,使結構「橫向」增加
若是想擴展功能,使其可以處理待辦事宜和聯繫人,那應該讓它進行縱向增加
CommsManager抽象類定義了用於生成3個產品(ApptEncoder、TtdEncoder、ContactEncoder)的接口,咱們須要先實現一個具體的建立者,而後才能建立一個特定類型的具體產品,下圖模型建立了BloggsCal格式的建立
下面是CommsManager和BloggsCommsManager的代碼
abstract class CommsManager{ abstract function getHeaderText(); abstract function getApptEncoder(); abstract function getTtdEncoder(); abstract function getContactEncoder(); abstract function getFooterText(); } class BloggsCommsManager extends CommsManager{ function getHeaderText(){ return "BloggsCal header\n"; } function getApptEncoder(){ return new BloggsApptEncoder(); } function getTtdEncoder(){ return new BloggsTtdEncoder(); } function getContactEncoder(){ return new BloggsContactEncoder(); } function getFooterText(){ return "BloggsCal footer\n"; } }
在這個例子中使用了工廠方法模式,getContactEncoder()是CommsManager的抽象方法,並在BloggsCommManager中實現。設計模式間常常會這樣寫做:一個模式建立能夠把它本身引入到另外一個模式的上下文環境中,咱們加入了對MegaCal格式的支持
這樣的模式帶來了什麼?
1.系統與實現的細節分離開來,咱們能夠在實例中添加移除任意樹木的編碼格式而不會影響系統
2.對系統中功能相關的元素強制進行組合,所以經過使用BloggsCommsManager,能夠確保值使用與BloggsCal相關的類
3.添加新產品比較麻煩,不只要建立新產品的具體實現,並且必須修改抽象建立者和它的每個具體實現
咱們能夠建立一個使用標誌來決定返回什麼對象的單一make()方法,而不用給每一個工廠方法建立獨立的方法,以下
abstract class CommsManager{ const APPT = 1; const TTD = 2; const CONTACT = 3; abstract function getHeaderText(); abstract function make($flag_int); abstract function getFooterText(); } class BloggsCommsManager extends CommsManager{ function getHeaderText(){ return "BloggsCal header"; } function make($flag_int){ switch($flag_int){ case self::APPT: return new BloggsApptEncoder(); case self::CONTACT: return new BloggsContactEncoder(); case self::TTD: return new BloggsTtdEncoder(); } } function getFooterText(){ return "BloggsCal footer\n"; } }
類的接口更加緊湊,但也有代價,在使用工廠方法時,咱們定義了一個清晰的接口強制全部具體工廠對象遵循它,而使用丹儀的make()方法,咱們必須在全部的具體建立者中支持全部的產品對象。每一個具體建立者都必須實現相同的標誌檢測(flag),客戶類沒法肯定具體的建立者是否能夠生成全部產品,由於make方法須要對每種狀況進行考慮並進行選擇