摘要:上章講的是創(chuàng)建型的設(shè)計(jì)模式,工廠方法上,這次要講的是另一本書(shū)關(guān)于工廠方法的一些概念以及案例模型等等。工廠方法模式假設(shè)你有一個(gè)關(guān)于個(gè)人事務(wù)管理的項(xiàng)目,功能之一是管理預(yù)約對(duì)象。
上章講的是創(chuàng)建型的設(shè)計(jì)模式,工廠方法(上),這次要講的是另一本書(shū)關(guān)于工廠方法的一些概念以及案例、模型等等。就像電影“風(fēng)雨哈佛路”中那個(gè)老師提問(wèn),為什么要用另外的一張一張紙質(zhì)資料,而不直接用書(shū)籍。女主回答說(shuō),因?yàn)椴煌馁Y料匯集了不同人的思想。
工廠方法模式假設(shè)你有一個(gè)關(guān)于個(gè)人事務(wù)管理的項(xiàng)目,功能之一是管理預(yù)約對(duì)象(Appointment)?,F(xiàn)在要和另一個(gè)公司建立關(guān)系,需要一個(gè)叫做BloggsCal的格式來(lái)和他們交流預(yù)約相關(guān)的數(shù)據(jù)。但是你將來(lái)可能要面對(duì)更多的數(shù)據(jù)格式
在接口上可以立即定義兩個(gè)類,
1.Class ApptEncoder:數(shù)據(jù)編碼器,將Appointment轉(zhuǎn)換成一個(gè)專有格式
2.Class CommsManager:管理員類,用來(lái)獲取數(shù)據(jù)編碼器,并使用編碼器進(jìn)行第三方通信
使用模型屬于來(lái)描述的話,CommsManager就是創(chuàng)建者(Creator),而ApptEncoder是產(chǎn)品(product)
那么如何得到一個(gè)具體的ApptEncoder對(duì)象?
CommsManager類負(fù)責(zé)生成BloggsApptEncoder對(duì)象,但是當(dāng)你和合作方關(guān)系改變,被要求轉(zhuǎn)換系統(tǒng)來(lái)使用一個(gè)新的格式MegaCal時(shí),那么代碼就需要做另外的改變了
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();
在類中我們使用常量標(biāo)志定義了腳本可能運(yùn)行的兩個(gè)模式:MEGA和BLOGGS,在getApptEncoder()方法中使用switch語(yǔ)句來(lái)檢查$mode屬性,并實(shí)例化相關(guān)編碼器
但是這種方法還有一種小缺陷,通常情況下,創(chuàng)建對(duì)象需要指定條件,但是有時(shí)候條件語(yǔ)句會(huì)被當(dāng)作Awful的“Code taste”,因?yàn)?strong>可能會(huì)導(dǎo)致重復(fù)的條件語(yǔ)句蔓延在代碼中。我們知道創(chuàng)建者已經(jīng)能夠提供交流日歷數(shù)據(jù)的功能,但是如果合作方要求提供頁(yè)眉和頁(yè)腳來(lái)約束每次預(yù)約,那該怎么辦?
結(jié)果是,你需要在上面的代碼中加入新的方法
function getHeaderText(){ switch($this->mode){ case (self::MEGA): return "This is Mega format header! "; default: return "This is Bloggs format header! "; } }
Obviously,這會(huì)使得它在getApptEncoder()方法同時(shí)使用時(shí),重復(fù)地使用了switch判斷,一旦客戶要增加其它需求,那工作量以及冗余程度會(huì)更重
總結(jié)一下當(dāng)前需要思考的:
1.在代碼運(yùn)行時(shí)我們才知道要生成的對(duì)象類型(BloggsApptEncoder或者是MegaApptEncoder)
2.我們需要能夠相對(duì)輕松地加入一些新的產(chǎn)品類型(如新的業(yè)務(wù)處理方式SyncML)
3.每一個(gè)產(chǎn)品類型都可定制特定的功能(如上文提到的頁(yè)眉頁(yè)腳)
另外注意我們使用的條件語(yǔ)句,其實(shí)可以被多態(tài)替代,而工廠方法模式恰好能讓我們用繼承和多態(tài)來(lái)封裝具體產(chǎn)品的創(chuàng)建,黃菊花說(shuō),我們要為每種協(xié)議創(chuàng)建CommsManager的每一個(gè)子類,而每一個(gè)子類都要實(shí)現(xiàn)getApptEncoder方法
實(shí)現(xiàn)工廠方法模式把創(chuàng)建者類與要生產(chǎn)的產(chǎn)品分離開(kāi)來(lái)。創(chuàng)建者是一個(gè)工廠類,其中定義了用于生成產(chǎn)品對(duì)象的類方法,如果沒(méi)有提供默認(rèn)實(shí)現(xiàn),那么就由創(chuàng)建者類的子類來(lái)執(zhí)行實(shí)例化。一般來(lái)說(shuō),就是創(chuàng)建者類的每個(gè)子類實(shí)例化一個(gè)相應(yīng)產(chǎn)品子類
所以我們把CommsManager重新指定為抽象類,這樣就可以得到一個(gè)靈活的父類,并把所有特定協(xié)議相關(guān)的代碼放到具體的子類中
下面是簡(jiǎn)化過(guò)的代碼:
abstract class ApptEncoder{ abstract function encode(); } class BloggsApptEncoder extends ApptEncoder{ function encode(){ return "Appointment data encode in BloggsCal format! "; } } 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"; } }
現(xiàn)在當(dāng)我們要求實(shí)現(xiàn)MegaCal時(shí),只需要給CommsManager抽象類寫(xiě)一個(gè)新的實(shí)現(xiàn)
注意到上面的創(chuàng)建者類與產(chǎn)品的層次結(jié)構(gòu)很相似,這是使用工廠方法模式的常見(jiàn)結(jié)果,形成了一種特殊的代碼重復(fù)。另一個(gè)問(wèn)題是該模式可能會(huì)導(dǎo)致不必要的子類化,如果你為創(chuàng)建者創(chuàng)建子類的原因是為了實(shí)現(xiàn)工廠方法模式,那么最好再考慮一下(這就是為什么在例子中引入頁(yè)眉頁(yè)腳)
抽象工廠模式上面例子中我們只關(guān)注了預(yù)約功能。
我們通過(guò)加入更多編碼格式,使結(jié)構(gòu)“橫向”增長(zhǎng)
如果想擴(kuò)展功能,使其能夠處理待辦事宜和聯(lián)系人,那應(yīng)該讓它進(jìn)行縱向增長(zhǎng)
CommsManager抽象類定義了用于生成3個(gè)產(chǎn)品(ApptEncoder、TtdEncoder、ContactEncoder)的接口,我們需要先實(shí)現(xiàn)一個(gè)具體的創(chuàng)建者,然后才能創(chuàng)建一個(gè)特定類型的具體產(chǎn)品,下圖模型創(chuàng)建了BloggsCal格式的創(chuàng)建
下面是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 "; } function getApptEncoder(){ return new BloggsApptEncoder(); } function getTtdEncoder(){ return new BloggsTtdEncoder(); } function getContactEncoder(){ return new BloggsContactEncoder(); } function getFooterText(){ return "BloggsCal footer "; } }
在這個(gè)例子中使用了工廠方法模式,getContactEncoder()是CommsManager的抽象方法,并在BloggsCommManager中實(shí)現(xiàn)。設(shè)計(jì)模式間經(jīng)常會(huì)這樣寫(xiě)作:一個(gè)模式創(chuàng)建可以把它自己引入到另一個(gè)模式的上下文環(huán)境中,我們加入了對(duì)MegaCal格式的支持
這樣的模式帶來(lái)了什么?
1.系統(tǒng)與實(shí)現(xiàn)的細(xì)節(jié)分離開(kāi)來(lái),我們可以在實(shí)例中添加移除任意樹(shù)木的編碼格式而不會(huì)影響系統(tǒng)
2.對(duì)系統(tǒng)中功能相關(guān)的元素強(qiáng)制進(jìn)行組合,因此通過(guò)使用BloggsCommsManager,可以確保值使用與BloggsCal相關(guān)的類
3.添加新產(chǎn)品比較麻煩,不僅要?jiǎng)?chuàng)建新產(chǎn)品的具體實(shí)現(xiàn),而且必須修改抽象創(chuàng)建者和它的每一個(gè)具體實(shí)現(xiàn)
我們可以創(chuàng)建一個(gè)使用標(biāo)志來(lái)決定返回什么對(duì)象的單一make()方法,而不用給每個(gè)工廠方法創(chuàng)建獨(dú)立的方法,如下
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 "; } }
類的接口更加緊湊,但也有代價(jià),在使用工廠方法時(shí),我們定義了一個(gè)清晰的接口強(qiáng)制所有具體工廠對(duì)象遵循它,而使用丹儀的make()方法,我們必須在所有的具體創(chuàng)建者中支持所有的產(chǎn)品對(duì)象。每個(gè)具體創(chuàng)建者都必須實(shí)現(xiàn)相同的標(biāo)志檢測(cè)(flag),客戶類無(wú)法確定具體的創(chuàng)建者是否可以生成所有產(chǎn)品,因?yàn)閙ake方法需要對(duì)每種情況進(jìn)行考慮并進(jìn)行選擇
本章參考《深入PHP:面向?qū)ο?、模式與實(shí)踐》第9章
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/21259.html
摘要:利用工廠方法模式,請(qǐng)求者發(fā)出請(qǐng)求,而不具體創(chuàng)建產(chǎn)品。正是因?yàn)檫@個(gè)原因,使用工廠方法模式可以簡(jiǎn)化復(fù)雜的創(chuàng)建過(guò)程,關(guān)鍵就在于它在維持一個(gè)公共接口。 創(chuàng)建型設(shè)計(jì)模式 包括以下五種: 抽象工廠 生成器 工廠方法 原型 單例 我們選擇工廠方法和原型模式作為將用PHP實(shí)現(xiàn)的創(chuàng)建型設(shè)計(jì)的例子工廠方法模式是這5個(gè)設(shè)計(jì)模式中唯一的一種類設(shè)計(jì)模式原型模式屬于對(duì)象類模式,可以使用PHP_clone方法實(shí)...
摘要:設(shè)計(jì)模式設(shè)計(jì)模式基本原則設(shè)計(jì)原則按接口而不是按實(shí)現(xiàn)來(lái)編程按接口而不是按實(shí)現(xiàn)編程是指,要將變量設(shè)置為一個(gè)抽象類或接口數(shù)據(jù)類型的實(shí)例,而不是一個(gè)具體實(shí)現(xiàn)的實(shí)例。例如父類的一個(gè)改變會(huì)逐級(jí)向下傳遞給子類實(shí)現(xiàn),這可能會(huì)影響子類使用的某個(gè)算法。 設(shè)計(jì)模式 設(shè)計(jì)模式基本原則 設(shè)計(jì)原則 ① : 按接口而不是按實(shí)現(xiàn)來(lái)編程 按接口而不是按實(shí)現(xiàn)編程是指,要將變量設(shè)置為一個(gè)抽象類或接口數(shù)據(jù)類型的實(shí)例,而不是一...
摘要:又稱為多態(tài)性工廠模式或虛擬構(gòu)造子模式。簡(jiǎn)單工廠模式簡(jiǎn)單工廠模式簡(jiǎn)單工廠模式又稱為靜態(tài)工廠方法模式,它屬于類創(chuàng)建型模式。多態(tài)性設(shè)計(jì)工廠方法模式之所以又被稱為多態(tài)工廠模式,是因?yàn)樗械木唧w工廠類都具有同一抽象父類。 點(diǎn)擊進(jìn)入我的博客 2.1 簡(jiǎn)單工廠模式 2.1.1 工廠模式的幾種形態(tài) 工廠模式主要用一下幾種形態(tài): 簡(jiǎn)單工廠(Simple Factory):專門定義一個(gè)類來(lái)負(fù)責(zé)創(chuàng)建其他...
摘要:維基百科在軟件工程中,創(chuàng)建型設(shè)計(jì)模式是用于解決對(duì)象創(chuàng)建機(jī)制,嘗試在指定場(chǎng)景下使用合理的方式來(lái)創(chuàng)建對(duì)象的設(shè)計(jì)模式。維基百科說(shuō)建造者模式是一種對(duì)象創(chuàng)建軟件設(shè)計(jì)模式,其目的是找到一種解決方案,以解決可伸縮構(gòu)造函數(shù)的反模式。 1.創(chuàng)建型設(shè)計(jì)模式2.結(jié)構(gòu)型設(shè)計(jì)模式3.行為型設(shè)計(jì)模式 創(chuàng)建型設(shè)計(jì)模式 簡(jiǎn)而言之 創(chuàng)建型設(shè)計(jì)模式關(guān)注的是如何實(shí)例化一個(gè)或者一組相關(guān)的對(duì)象。 維基百科 在軟件工程中,創(chuàng)建型...
摘要:創(chuàng)建型模式主要有以下五種簡(jiǎn)單工廠模式和工廠方法模式抽象工廠模式單例模式建造者模式原型模式在設(shè)計(jì)模式一書(shū)中將工廠模式分為兩類工廠方法模式與抽象工廠模式。 一、 設(shè)計(jì)模式(Design pattern)是什么 設(shè)計(jì)模式是一套被反復(fù)使用、多數(shù)人知曉、經(jīng)過(guò)分類編目的代碼設(shè)計(jì)的經(jīng)驗(yàn)總結(jié)。使用設(shè)計(jì)模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 二、 為什么會(huì)有設(shè)計(jì)模式 在軟件開(kāi)發(fā)過(guò)...
閱讀 3744·2021-11-25 09:43
閱讀 2612·2021-11-18 13:11
閱讀 2238·2019-08-30 15:55
閱讀 3284·2019-08-26 11:58
閱讀 2837·2019-08-26 10:47
閱讀 2243·2019-08-26 10:20
閱讀 1283·2019-08-23 17:59
閱讀 3016·2019-08-23 15:54