另外五個(gè) PHP 設(shè)計(jì)模式
級別: 中級
Nathan A. Good
(
mail@nathanagood.com
), 高級信息工程師, 顧問
2008 年 4 月 28 日
PHP V5 的面向?qū)ο筇匦允鼓軌驅(qū)崿F(xiàn)設(shè)計(jì)模式來改進(jìn)代碼設(shè)計(jì)。通過這種方式改進(jìn)代碼設(shè)計(jì),代碼在進(jìn)行修改時(shí)將變得更加易讀、更易維護(hù)且更加健壯。<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
設(shè)計(jì)模式 一書介紹了很多此類概念。當(dāng)時(shí),我還在學(xué)習(xí)面向?qū)ο?(OO),因此我發(fā)現(xiàn)那本書中有許多概念都很難領(lǐng)會(huì)。但是,隨著越來越熟悉 OO 概念 —— 尤其是接口和繼承的使用 —— 我開始看到設(shè)計(jì)模式中的實(shí)際價(jià)值。作為一名應(yīng)用程序開發(fā)人員,即使從不了解任何模式或者如何及何時(shí)使用這些模式,對您的職業(yè)生涯也沒有什么大的影響。但是,我發(fā)現(xiàn)了解這些模式以及 developerWorks 文章 “五種常見 PHP 設(shè)計(jì)模式” 中介紹的那些模式的優(yōu)秀知識后(請參閱 參考資料 ),您可以完成兩件事情:
有句諺語說得好:“當(dāng)您手中拿著一把錘子時(shí),所有事物看上去都像釘子”。當(dāng)您認(rèn)為自己找到一個(gè)優(yōu)秀模式時(shí),您可能會(huì)嘗試到處使用它,即使在不應(yīng)當(dāng)使用它的位置。記住您必須考慮正在學(xué)習(xí)的模式的使用目的,不要為了使用模式而把這些模式強(qiáng)行應(yīng)用到應(yīng)用程序的各個(gè)部分中。
本文將介紹可用于改進(jìn) PHP 代碼的五個(gè)模式。每個(gè)模式都將介紹一個(gè)特定場景??梢栽? 下載 部分中獲得這些模式的 PHP 代碼。
要發(fā)揮本文的最大功效并使用示例,需要在計(jì)算機(jī)中安裝以下軟件:
- PHP V5 或更高版本(本文是使用 PHP V5.2.4 撰寫的)
- 壓縮程序,例如 WinZIP(用于壓縮可下載的代碼歸檔)
注: 雖然您也可以使用純文本編輯器,但是我發(fā)現(xiàn)擁有語法高亮顯示和語法糾錯(cuò)功能的編輯器真的很有幫助。本文中的示例是使用 Eclipse PHP Development Tools (PDT) 編寫的。
![]() ![]() |
![]()
|
在需要將一類對象轉(zhuǎn)換成另一類對象時(shí),請使用適配器模式。通常,開發(fā)人員通過一系列賦值代碼來處理此過程,如清單 1 所示。適配器模式是整理此類代碼并在其他位置重用所有賦值代碼的優(yōu)秀方法。此外,它還將隱藏賦值代碼,如果同時(shí)還要設(shè)定格式,這樣可以極大地簡化工作。
class AddressDisplay
{
private $addressType;
private $addressText;
public function setAddressType($addressType)
{
$this->addressType = $addressType;
}
public function getAddressType()
{
return $this->addressType;
}
public function setAddressText($addressText)
{
$this->addressText = $addressText;
}
public function getAddressText()
{
return $this->addressText;
}
}
class EmailAddress
{
private $emailAddress;
public function getEmailAddress()
{
return $this->emailAddress;
}
public function setEmailAddress($address)
{
$this->emailAddress = $address;
}
}
$emailAddress = new EmailAddress();
/* Populate the EmailAddress object */
$address = new AddressDisplay();
/* Here's the assignment code, where I'm assigning values from one object to another... */
$address->setAddressType("email");
$address->setAddressText($emailAddress->getEmailAddress());
|
此示例將使用
AddressDisplay
對象把地址顯示給用戶。
AddressDisplay
對象有兩部分:地址類型和一個(gè)格式化的地址字符串。
在實(shí)現(xiàn)模式(參見清單 2)后,PHP 腳本將不再需要擔(dān)心如何把
EmailAddress
對象轉(zhuǎn)換成
AddressDisplay
對象。那是件好事,尤其是在
AddressDisplay
對象發(fā)生更改時(shí)或者控制如何把
EmailAddress
對象轉(zhuǎn)換成
AddressDisplay
對象的規(guī)則發(fā)生更改時(shí)。記住,以模塊化風(fēng)格設(shè)計(jì)代碼的主要優(yōu)點(diǎn)之一就是,在業(yè)務(wù)領(lǐng)域發(fā)生一些更改時(shí)或者需要向軟件中添加新功能時(shí)盡可能少的使用更改。即使在執(zhí)行普通任務(wù)(例如把一個(gè)對象的屬性值賦給另一個(gè)對象)時(shí),也請考慮使用此模式。
class EmailAddressDisplayAdapter extends AddressDisplay { public function __construct($emailAddr) { $this->setAddressType("email"); $this->setAddressText($emailAddr->getEmailAddress()); } } $email = new EmailAddress(); $email->setEmailAddress("user@example.com"); $address = new EmailAddressDisplayAdapter($email); echo($address->getAddressType() . "/n") ; echo($address->getAddressText()); |
圖 1 顯示了適配器模式的類圖。
編寫適配器的替代方法 —— 并且是推薦方法 —— 是實(shí)現(xiàn)一個(gè)接口來修改行為,而不是擴(kuò)展對象。這是一種非常干凈的、創(chuàng)建適配器的方法并且沒有擴(kuò)展對象的缺點(diǎn)。使用接口的缺點(diǎn)之一是需要把實(shí)現(xiàn)添加到適配器類中,如圖 2 所示:
![]() ![]() |
![]()
|
迭代器模式將提供一種通過對象集合或?qū)ο髷?shù)組封裝迭代的方法。如果需要遍歷集合中不同類型的對象,則使用這種模式尤為便利。
查看上面清單 1 中的電子郵件和物理地址示例。在添加迭代器模式之前,如果要遍歷個(gè)人地址,則可能要遍歷物理地址并顯示這些地址,然后遍歷個(gè)人電子郵件地址并顯示這些地址,然后遍歷個(gè)人 IM 地址并顯示這些地址。非常復(fù)雜的遍歷!
相反,通過實(shí)現(xiàn)迭代器,您只需要調(diào)用
while($itr->hasNext())
并處理下一個(gè)條目
$itr->next()
返回。清單 3 中顯示了一個(gè)迭代器示例。迭代器功能強(qiáng)大,因?yàn)槟梢蕴砑右闅v的新類型條目,并且無需更改遍歷條目的代碼。例如,在
Person
示例中,可以添加 IM 地址數(shù)組;只需更新迭代器,無需更改遍歷地址的任何代碼。
class PersonAddressIterator implements AddressIterator { private $emailAddresses; private $physicalAddresses; private $position; public function __construct($emailAddresses) { $this->emailAddresses = $emailAddresses; $this->position = 0; } public function hasNext() { if ($this->position >= count($this->emailAddresses) || $this->emailAddresses[$this->position] == null) { return false; } else { return true; } } public function next() { $item = $this->emailAddresses[$this->position]; $this->position = $this->position + 1; return $item; } } |
如果把
Person
對象修改為返回
AddressIterator
接口的實(shí)現(xiàn),則在將實(shí)現(xiàn)擴(kuò)展為遍歷附加對象時(shí)無需修改使用迭代器的應(yīng)用程序代碼。您可以使用一個(gè)混合迭代器,它封裝了遍歷清單 3 中列出的每種地址的迭代器。本文提供了此類應(yīng)用示例(請參閱
下載
)。
圖 3 顯示了迭代器模式的類圖。
![]() ![]() |
![]()
|
考慮清單 4 中的代碼樣例。這段代碼的目的是要把許多功能添加到 Build Your Own Car 站點(diǎn)的汽車中。每個(gè)汽車模型都有更多功能及相關(guān)價(jià)格。如果只針對兩個(gè)模型,使用
if then
語句添加這些功能十分平常。但是,如果出現(xiàn)了新模型,則必須返回查看代碼并確保語句對新模型工作正常。
require('classes.php'); $auto = new Automobile(); $model = new BaseAutomobileModel(); $model = new SportAutomobileModel($model); $model = new TouringAutomobileModel($model); $auto->setModel($model); $auto->printDescription(); |
進(jìn)入裝飾器模式,該模式允許您通過一個(gè)優(yōu)秀整潔的類將此功能添加到
AutomobileModel
。每個(gè)類僅僅關(guān)注其價(jià)格、選項(xiàng)以及添加到基本模型的方式。
圖 4 顯示了裝飾器模式的類圖。
裝飾器模式的優(yōu)點(diǎn)是可以輕松地同時(shí)跟蹤庫的多個(gè)裝飾器。
如果您擁有流對象的使用經(jīng)驗(yàn),則一定使用過裝飾器。大多數(shù)流結(jié)構(gòu)(例如輸出流)都是接受基本輸入流的裝飾器,然后通過添加附加功能來裝飾它 —— 例如從文件輸入流、從緩沖區(qū)輸入流,等等。
![]() ![]() |
![]()
|
委托模式將提供一種基于各種條件委托行為的方法??紤]清單 5 中的代碼。這段代碼包含幾個(gè)條件。根據(jù)條件,代碼將選擇相應(yīng)類型的對象來處理請求。
pkg = new Package("Heavy Package"); $pkg->setWeight(100); if ($pkg->getWeight() > 99) { echo( "Shipping " . $pkg->getDescription() . " by rail."); } else { echo("Shipping " . $pkg->getDescription() . " by truck"); } |
使用委托模式,對象將內(nèi)在化(internalize)此發(fā)送過程,方法為在調(diào)用如清單 6 中的
useRail()
之類的方法時(shí)設(shè)置對相應(yīng)對象的內(nèi)部引用。如果處理各個(gè)包的條件發(fā)生更改或者使用新的送貨類型時(shí),則使用此模式尤為便利。
require_once('classes.php'); $pkg = new Package("Heavy Package"); $pkg->setWeight(100); $shipper = new ShippingDelegate(); if ($pkg->getWeight() > 99) { $shipper->useRail(); } $shipper->deliver($pkg); |
委托將通過調(diào)用
useRail()
或
useTruck()
方法來切換處理工作的類,從而提供動(dòng)態(tài)更改行為的優(yōu)點(diǎn)。
圖 5 顯示了委托模式的類圖。
![]() ![]() |
![]()
|
狀態(tài)模式類似于命令模式,但是意圖截然不同??紤]下面的代碼。
class Robot { private $state; public function powerUp() { if (strcmp($state, "poweredUp") == 0) { echo("Already powered up.../n"); /* Implementation... */ } else if ( strcmp($state, "powereddown") == 0) { echo("Powering up now.../n"); /* Implementation... */ } } public function powerDown() { if (strcmp($state, "poweredUp") == 0) { echo("Powering down now.../n"); /* Implementation... */ } else if ( strcmp($state, "powereddown") == 0) { echo("Already powered down.../n"); /* Implementation... */ } } /* etc... */ } |
在此清單中,PHP 代碼表示變成一輛汽車的強(qiáng)大機(jī)器人的操作系統(tǒng)。機(jī)器人可以啟動(dòng)、關(guān)閉、由汽車變成機(jī)器人以及由機(jī)器人變成汽車。代碼現(xiàn)已就緒,但是您會(huì)看到如果任何規(guī)則發(fā)生更改或者添加另一個(gè)狀態(tài)則會(huì)變得十分復(fù)雜。
現(xiàn)在查看清單 8,其中提供了相同的邏輯處理機(jī)器人的狀態(tài),但是這一次把邏輯放入狀態(tài)模式。清單 8 中的代碼完成的工作與初始代碼相同,但是用于處理狀態(tài)的邏輯已經(jīng)被放入每個(gè)狀態(tài)的一個(gè)對象中。為了演示使用設(shè)計(jì)模式的優(yōu)點(diǎn),假定不久以后,這些機(jī)器人發(fā)現(xiàn)它們不應(yīng)在處于機(jī)器人模式時(shí)關(guān)閉。實(shí)際上,如果它們關(guān)閉,它們必須先切換到汽車模式。如果它們已經(jīng)處于汽車模式下,則機(jī)器人將關(guān)閉。使用狀態(tài)模式,對代碼的更改十分微小。
清單 8. 使用狀態(tài)模式處理機(jī)器人的狀態(tài)
$robot = new Robot(); echo("/n"); $robot->powerUp(); echo("/n"); $robot->turnIntoRobot(); echo("/n"); $robot->turnIntoRobot(); /* This one will just give me a message */ echo("/n"); $robot->turnIntoVehicle(); echo("/n"); |
清單 9. 對一個(gè)狀態(tài)對象的微小更改
class NormalRobotState implements RobotState
{
private $robot;
public function __construct($robot)
{
$this->robot = $robot;
}
public function powerUp()
{
/* implementation... */
}
public function powerDown()
{
/* First, turn into a vehicle */ $this->robot->setState(new VehicleRobotState($this->robot)); $this->robot->powerDown();
}
public function turnIntoVehicle()
{
/* implementation... */
}
public function turnIntoRobot()
{
/* implementation... */
}
}
|
圖 6 中一個(gè)不太明顯的地方就是狀態(tài)模式中的每個(gè)對象都有對上下文對象(機(jī)器人)的引用,因此每個(gè)對象都可以把狀態(tài)提升到相應(yīng)的狀態(tài)。
結(jié)束語
在 PHP 代碼中使用設(shè)計(jì)模式可以使代碼更容易閱讀、更易維護(hù)。通過使用已經(jīng)建立的模式,您將從通用的設(shè)計(jì)結(jié)構(gòu)中獲益,從而允許團(tuán)隊(duì)的其他開發(fā)人員了解代碼的意圖。它還使您可以從其他設(shè)計(jì)者完成的工作中獲益,因此無需從失敗的設(shè)計(jì)理念中吸取教訓(xùn)。
來源: http://www.ibm.com/developerworks/cn/opensource/os-php-designpatterns
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
