另外五個 PHP 設計模式
級別: 中級
Nathan A. Good
(
mail@nathanagood.com
), 高級信息工程師, 顧問
2008 年 4 月 28 日
PHP V5 的面向對象特性使您能夠實現設計模式來改進代碼設計。通過這種方式改進代碼設計,代碼在進行修改時將變得更加易讀、更易維護且更加健壯。<!--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-->
設計模式 一書介紹了很多此類概念。當時,我還在學習面向對象 (OO),因此我發現那本書中有許多概念都很難領會。但是,隨著越來越熟悉 OO 概念 —— 尤其是接口和繼承的使用 —— 我開始看到設計模式中的實際價值。作為一名應用程序開發人員,即使從不了解任何模式或者如何及何時使用這些模式,對您的職業生涯也沒有什么大的影響。但是,我發現了解這些模式以及 developerWorks 文章 “五種常見 PHP 設計模式” 中介紹的那些模式的優秀知識后(請參閱 參考資料 ),您可以完成兩件事情:
有句諺語說得好:“當您手中拿著一把錘子時,所有事物看上去都像釘子”。當您認為自己找到一個優秀模式時,您可能會嘗試到處使用它,即使在不應當使用它的位置。記住您必須考慮正在學習的模式的使用目的,不要為了使用模式而把這些模式強行應用到應用程序的各個部分中。
本文將介紹可用于改進 PHP 代碼的五個模式。每個模式都將介紹一個特定場景。可以在 下載 部分中獲得這些模式的 PHP 代碼。
要發揮本文的最大功效并使用示例,需要在計算機中安裝以下軟件:
- PHP V5 或更高版本(本文是使用 PHP V5.2.4 撰寫的)
- 壓縮程序,例如 WinZIP(用于壓縮可下載的代碼歸檔)
注: 雖然您也可以使用純文本編輯器,但是我發現擁有語法高亮顯示和語法糾錯功能的編輯器真的很有幫助。本文中的示例是使用 Eclipse PHP Development Tools (PDT) 編寫的。
![]() ![]() |
![]()
|
在需要將一類對象轉換成另一類對象時,請使用適配器模式。通常,開發人員通過一系列賦值代碼來處理此過程,如清單 1 所示。適配器模式是整理此類代碼并在其他位置重用所有賦值代碼的優秀方法。此外,它還將隱藏賦值代碼,如果同時還要設定格式,這樣可以極大地簡化工作。
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
對象有兩部分:地址類型和一個格式化的地址字符串。
在實現模式(參見清單 2)后,PHP 腳本將不再需要擔心如何把
EmailAddress
對象轉換成
AddressDisplay
對象。那是件好事,尤其是在
AddressDisplay
對象發生更改時或者控制如何把
EmailAddress
對象轉換成
AddressDisplay
對象的規則發生更改時。記住,以模塊化風格設計代碼的主要優點之一就是,在業務領域發生一些更改時或者需要向軟件中添加新功能時盡可能少的使用更改。即使在執行普通任務(例如把一個對象的屬性值賦給另一個對象)時,也請考慮使用此模式。
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 顯示了適配器模式的類圖。
編寫適配器的替代方法 —— 并且是推薦方法 —— 是實現一個接口來修改行為,而不是擴展對象。這是一種非常干凈的、創建適配器的方法并且沒有擴展對象的缺點。使用接口的缺點之一是需要把實現添加到適配器類中,如圖 2 所示:
![]() ![]() |
![]()
|
迭代器模式將提供一種通過對象集合或對象數組封裝迭代的方法。如果需要遍歷集合中不同類型的對象,則使用這種模式尤為便利。
查看上面清單 1 中的電子郵件和物理地址示例。在添加迭代器模式之前,如果要遍歷個人地址,則可能要遍歷物理地址并顯示這些地址,然后遍歷個人電子郵件地址并顯示這些地址,然后遍歷個人 IM 地址并顯示這些地址。非常復雜的遍歷!
相反,通過實現迭代器,您只需要調用
while($itr->hasNext())
并處理下一個條目
$itr->next()
返回。清單 3 中顯示了一個迭代器示例。迭代器功能強大,因為您可以添加要遍歷的新類型條目,并且無需更改遍歷條目的代碼。例如,在
Person
示例中,可以添加 IM 地址數組;只需更新迭代器,無需更改遍歷地址的任何代碼。
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
接口的實現,則在將實現擴展為遍歷附加對象時無需修改使用迭代器的應用程序代碼。您可以使用一個混合迭代器,它封裝了遍歷清單 3 中列出的每種地址的迭代器。本文提供了此類應用示例(請參閱
下載
)。
圖 3 顯示了迭代器模式的類圖。
![]() ![]() |
![]()
|
考慮清單 4 中的代碼樣例。這段代碼的目的是要把許多功能添加到 Build Your Own Car 站點的汽車中。每個汽車模型都有更多功能及相關價格。如果只針對兩個模型,使用
if then
語句添加這些功能十分平常。但是,如果出現了新模型,則必須返回查看代碼并確保語句對新模型工作正常。
require('classes.php'); $auto = new Automobile(); $model = new BaseAutomobileModel(); $model = new SportAutomobileModel($model); $model = new TouringAutomobileModel($model); $auto->setModel($model); $auto->printDescription(); |
進入裝飾器模式,該模式允許您通過一個優秀整潔的類將此功能添加到
AutomobileModel
。每個類僅僅關注其價格、選項以及添加到基本模型的方式。
圖 4 顯示了裝飾器模式的類圖。
裝飾器模式的優點是可以輕松地同時跟蹤庫的多個裝飾器。
如果您擁有流對象的使用經驗,則一定使用過裝飾器。大多數流結構(例如輸出流)都是接受基本輸入流的裝飾器,然后通過添加附加功能來裝飾它 —— 例如從文件輸入流、從緩沖區輸入流,等等。
![]() ![]() |
![]()
|
委托模式將提供一種基于各種條件委托行為的方法。考慮清單 5 中的代碼。這段代碼包含幾個條件。根據條件,代碼將選擇相應類型的對象來處理請求。
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"); } |
使用委托模式,對象將內在化(internalize)此發送過程,方法為在調用如清單 6 中的
useRail()
之類的方法時設置對相應對象的內部引用。如果處理各個包的條件發生更改或者使用新的送貨類型時,則使用此模式尤為便利。
require_once('classes.php'); $pkg = new Package("Heavy Package"); $pkg->setWeight(100); $shipper = new ShippingDelegate(); if ($pkg->getWeight() > 99) { $shipper->useRail(); } $shipper->deliver($pkg); |
委托將通過調用
useRail()
或
useTruck()
方法來切換處理工作的類,從而提供動態更改行為的優點。
圖 5 顯示了委托模式的類圖。
![]() ![]() |
![]()
|
狀態模式類似于命令模式,但是意圖截然不同。考慮下面的代碼。
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 代碼表示變成一輛汽車的強大機器人的操作系統。機器人可以啟動、關閉、由汽車變成機器人以及由機器人變成汽車。代碼現已就緒,但是您會看到如果任何規則發生更改或者添加另一個狀態則會變得十分復雜。
現在查看清單 8,其中提供了相同的邏輯處理機器人的狀態,但是這一次把邏輯放入狀態模式。清單 8 中的代碼完成的工作與初始代碼相同,但是用于處理狀態的邏輯已經被放入每個狀態的一個對象中。為了演示使用設計模式的優點,假定不久以后,這些機器人發現它們不應在處于機器人模式時關閉。實際上,如果它們關閉,它們必須先切換到汽車模式。如果它們已經處于汽車模式下,則機器人將關閉。使用狀態模式,對代碼的更改十分微小。
$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. 對一個狀態對象的微小更改
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 中一個不太明顯的地方就是狀態模式中的每個對象都有對上下文對象(機器人)的引用,因此每個對象都可以把狀態提升到相應的狀態。
結束語
在 PHP 代碼中使用設計模式可以使代碼更容易閱讀、更易維護。通過使用已經建立的模式,您將從通用的設計結構中獲益,從而允許團隊的其他開發人員了解代碼的意圖。它還使您可以從其他設計者完成的工作中獲益,因此無需從失敗的設計理念中吸取教訓。
來源: http://www.ibm.com/developerworks/cn/opensource/os-php-designpatterns
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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