另外五個 PHP 設計模式

設計模式 一書介紹了不少此類概念。當時,我還在學習面向對象 (OO),所以我發現那本書中有許多概念都很難領會。可是,隨着愈來愈熟悉 OO 概念 —— 尤爲是接口和繼承的使用 —— 我開始看到設計模式中的實際價值。做爲一名應用程序開發人員,即便從不瞭解任何模式或者如何及什麼時候使用這些模式,對您的職業生涯也沒有什麼大的影響。可是,我發現瞭解這些模式以及 developerWorks 文章 「五種常見 PHP 設計模式」 中介紹的那些模式的優秀知識後(請參閱 參考資料),您能夠完成兩件事情:php

  • 啓用高帶寬會話設計模式

  • 若是瞭解設計模式,您將可以更快地構建可靠的 OO 應用程序。但當整個開發團隊知道各類模式時,您能夠忽然擁有很是高的帶寬會話。您再也不須要討論將處處使用的全部類。相反,您能夠與其餘人談論模式。「我要在這裏引用一個單例(singleton),而後使用迭代器遍歷對象集合,而後……」 比遍歷構成這些模式的類、方法和接口快不少。單是通訊效率一項就值得花時間以團隊的形式經過會話來研究模式。數組

  • 減小痛苦的教訓編輯器

  • 每一個設計模式都描述了一種通過驗證的解決常見問題的方法。所以,您無需擔憂設計是否是正確的,只要您已經選擇了提供所需優勢的模式。模塊化

缺陷

有句諺語說得好:「當您手中拿着一把錘子時,全部事物看上去都像釘子」。當您認爲本身找到一個優秀模式時,您可能會嘗試處處使用它,即便在不該當使用它的位置。記住您必須考慮正在學習的模式的使用目的,不要爲了使用模式而把這些模式強行應用到應用程序的各個部分中。學習

本文將介紹可用於改進 PHP 代碼的五個模式。每一個模式都將介紹一個特定場景。能夠在 下載 部分中得到這些模式的 PHP 代碼。ui

要求

要發揮本文的最大功效並使用示例,須要在計算機中安裝如下軟件:this

  • PHP V5 或更高版本(本文是使用 PHP V5.2.4 撰寫的)spa

  • 壓縮程序,例如 WinZIP(用於壓縮可下載的代碼歸檔)操作系統

注:雖然您也可使用純文本編輯器,可是我發現擁有語法高亮顯示和語法糾錯功能的編輯器真的頗有幫助。本文中的示例是使用 Eclipse PHP Development Tools (PDT) 編寫的。

適配器模式

在須要將一類對象轉換成另外一類對象時,請使用適配器模式。一般,開發人員經過一系列賦值代碼來處理此過程,如清單 1 所示。適配器模式是整理此類代碼並在其餘位置重用全部賦值代碼的優秀方法。此外,它還將隱藏賦值代碼,若是同時還要設定格式,這樣能夠極大地簡化工做。

清單 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 對象的規則發生更改時。記住,以模塊化風格設計代碼的主要優勢之一就是,在業務領域發生一些更改時或者須要向軟件中添加新功能時儘量少的使用更改。即便在執行普通任務(例如把一個對象的屬性值賦給另外一個對象)時,也請考慮使用此模式。

清單 2. 使用適配器模式
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 顯示了適配器模式的類圖。

圖 1. 適配器模式的類圖

適配器模式的類圖

替代方法

編寫適配器的替代方法 —— 而且是推薦方法 —— 是實現一個接口來修改行爲,而不是擴展對象。這是一種很是乾淨的、建立適配器的方法而且沒有擴展對象的缺點。使用接口的缺點之一是須要把實現添加到適配器類中,如圖 2 所示:

圖 2. 適配器模式(使用接口)

適配器模式(使用接口)

回頁首

迭代器模式

迭代器模式將提供一種經過對象集合或對象數組封裝迭代的方法。若是須要遍歷集合中不一樣類型的對象,則使用這種模式尤其便利。

查看上面清單 1 中的電子郵件和物理地址示例。在添加迭代器模式以前,若是要遍歷我的地址,則可能要遍歷物理地址並顯示這些地址,而後遍歷我的電子郵件地址並顯示這些地址,而後遍歷我的 IM 地址並顯示這些地址。很是複雜的遍歷!

相反,經過實現迭代器,您只須要調用 while($itr->hasNext()) 並處理下一個條目 $itr->next() 返回。清單 3 中顯示了一個迭代器示例。迭代器功能強大,由於您能夠添加要遍歷的新類型條目,而且無需更改遍歷條目的代碼。例如,在 Person 示例中,能夠添加 IM 地址數組;只需更新迭代器,無需更改遍歷地址的任何代碼。

清單 3. 使用迭代器模式遍歷對象
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 顯示了迭代器模式的類圖。

圖 3. 迭代器模式的類圖

迭代器模式的類圖

回頁首

裝飾器 (decorator) 模式

考慮清單 4 中的代碼樣例。這段代碼的目的是要把許多功能添加到 Build Your Own Car 站點的汽車中。每一個汽車模型都有更多功能及相關價格。若是隻針對兩個模型,使用 if then 語句添加這些功能十分日常。可是,若是出現了新模型,則必須返回查看代碼並確保語句對新模型工做正常。

清單 4. 使用裝飾器模式添加功能
require('classes.php');

$auto = new Automobile();

$model = new BaseAutomobileModel();

$model = new SportAutomobileModel($model);

$model = new TouringAutomobileModel($model);

$auto->setModel($model);

$auto->printDescription();

進入裝飾器模式,該模式容許您經過一個優秀整潔的類將此功能添加到 AutomobileModel。每一個類僅僅關注其價格、選項以及添加到基本模型的方式。

圖 4 顯示了裝飾器模式的類圖。

圖 4. 裝飾器模式的類圖

裝飾器模式的類圖

裝飾器模式的優勢是能夠輕鬆地同時跟蹤庫的多個裝飾器。

若是您擁有流對象的使用經驗,則必定使用過裝飾器。大多數流結構(例如輸出流)都是接受基本輸入流的裝飾器,而後經過添加附加功能來裝飾它 —— 例如從文件輸入流、從緩衝區輸入流,等等。

回頁首

委託模式

委託模式將提供一種基於各類條件委託行爲的方法。考慮清單 5 中的代碼。這段代碼包含幾個條件。根據條件,代碼將選擇相應類型的對象來處理請求。

清單 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() 之類的方法時設置對相應對象的內部引用。若是處理各個包的條件發生更改或者使用新的送貨類型時,則使用此模式尤其便利。

清單 6. 使用委託模式來發送送貨請求
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 顯示了委託模式的類圖。

圖 5. 委託模式的類圖

委託模式的類圖

回頁首

狀態模式

狀態模式相似於命令模式,可是意圖大相徑庭。考慮下面的代碼。

清單 7. 使用代碼來構建機器人
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 中的代碼完成的工做與初始代碼相同,可是用於處理狀態的邏輯已經被放入每一個狀態的一個對象中。爲了演示使用設計模式的優勢,假定不久之後,這些機器人發現它們不該在處於機器人模式時關閉。實際上,若是它們關閉,它們必須先切換到汽車模式。若是它們已經處於汽車模式下,則機器人將關閉。使用狀態模式,對代碼的更改十分微小。

清單 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 中一個不太明顯的地方就是狀態模式中的每一個對象都有對上下文對象(機器人)的引用,所以每一個對象均可以把狀態提高到相應的狀態。

圖 6. 狀態模式的類圖

狀態模式的類圖

回頁首

結束語

在 PHP 代碼中使用設計模式可使代碼更容易閱讀、更易維護。經過使用已經創建的模式,您將從通用的設計結構中獲益,從而容許團隊的其餘開發人員瞭解代碼的意圖。它還使您能夠從其餘設計者完成的工做中獲益,所以無需從失敗的設計理念中吸收教訓。

相關文章
相關標籤/搜索