更好的構(gòu)造開(kāi)發(fā)模板 五種PHP設(shè)計(jì)模式

如上所述,有時(shí)此類(lèi)模式在規(guī)模較小的環(huán)境中似乎有些大材小用。不過(guò),最好還是學(xué)習(xí)這種扎實(shí)的編碼形式,以便應(yīng)用于任意規(guī)模的項(xiàng)目中。
單元素模式某些應(yīng)用程序資源是獨(dú)占的,因?yàn)橛星抑挥幸粋€(gè)此類(lèi)型的資源。例如,通過(guò)數(shù)據(jù)庫(kù)句柄到數(shù)據(jù)庫(kù)的連接是獨(dú)占的。您希望在應(yīng)用程序中共享數(shù)據(jù)庫(kù)句柄,因?yàn)樵诒3诌B接打開(kāi)或關(guān)閉時(shí),它是一種開(kāi)銷(xiāo),在獲取單個(gè)頁(yè)面的過(guò)程中更是如此。單元素模式可以滿(mǎn)足此要求。如果應(yīng)用程序每次包含且僅包含一個(gè)對(duì)象,那么這個(gè)對(duì)象就是一個(gè)單元素(Singleton)。清單 3 中的代碼顯示了 PHP V5 中的一個(gè)數(shù)據(jù)庫(kù)連接單元素。清單 3. Singleton.php<?phprequire_once('DB.php');class DatabaseConnection{ public static function get() {static $db = null;if ( $db == null ) $db = new DatabaseConnection();return $db; } private $_handle = null; private function __construct() {$dsn = 'mysql://root:password@localhost/photos';$this->_handle =& DB::Connect( $dsn, array() ); } public function handle() {return $this->_handle; }}print( 'Handle = '.DatabaseConnection::get()->handle().'n' );print( 'Handle = '.DatabaseConnection::get()->handle().'n' );?>此代碼顯示名為 DatabaseConnection 的單個(gè)類(lèi)。您不能創(chuàng)建自已的 DatabaseConnection,因?yàn)闃?gòu)造函數(shù)是專(zhuān)用的。但使用靜態(tài) get 方法,您可以獲得且僅獲得一個(gè) DatabaseConnection 對(duì)象。此代碼的 UML 如圖 3 所示。圖 3. 數(shù)據(jù)庫(kù)連接單元素在兩次調(diào)用間,handle 方法返回的數(shù)據(jù)庫(kù)句柄是相同的,這就是最好的證明。您可以在命令行中運(yùn)行代碼來(lái)觀察這一點(diǎn)。% php singleton.php Handle = Object id #3Handle = Object id #3%返回的兩個(gè)句柄是同一對(duì)象。如果您在整個(gè)應(yīng)用程序中使用數(shù)據(jù)庫(kù)連接單元素,那么就可以在任何地方重用同一句柄。您可以使用全局變量存儲(chǔ)數(shù)據(jù)庫(kù)句柄,但是,該方法僅適用于較小的應(yīng)用程序。在較大的應(yīng)用程序中,應(yīng)避免使用全局變量,并使用對(duì)象和方法訪問(wèn)資源。
觀察者模式觀察者模式為您提供了避免組件之間緊密耦合的另一種方法。該模式非常簡(jiǎn)單:一個(gè)對(duì)象通過(guò)添加一個(gè)方法(該方法允許另一個(gè)對(duì)象,即觀察者 注冊(cè)自己)使本身變得可觀察。當(dāng)可觀察的對(duì)象更改時(shí),它會(huì)將消息發(fā)送到已注冊(cè)的觀察者。這些觀察者使用該信息執(zhí)行的操作與可觀察的對(duì)象無(wú)關(guān)。結(jié)果是對(duì)象可以相互對(duì)話,而不必了解原因。 一個(gè)簡(jiǎn)單示例是系統(tǒng)中的用戶(hù)列表。清單 4 中的代碼顯示一個(gè)用戶(hù)列表,添加用戶(hù)時(shí),它將發(fā)送出一條消息。添加用戶(hù)時(shí),通過(guò)發(fā)送消息的日志觀察者可以觀察此列表。清單 4. Observer.php<?phpinterface IObserver{ function onChanged( $sender, $args );}interface IObservable{ function addObserver( $observer );}class UserList implements IObservable{ private $_observers = array(); public function addCustomer( $name ) {foreach( $this->_observers as $obs )$obs->onChanged( $this, $name ); } public function addObserver( $observer ) {$this->_observers []= $observer; }}class UserListLogger implements IObserver{ public function onChanged( $sender, $args ) {echo( ''$args' added to user listn' ); }}$ul = new UserList();$ul->addObserver( new UserListLogger() );$ul->addCustomer( 'Jack' );?>此代碼定義四個(gè)元素:兩個(gè)接口和兩個(gè)類(lèi)。IObservable 接口定義可以被觀察的對(duì)象,UserList 實(shí)現(xiàn)該接口,以便將本身注冊(cè)為可觀察。IObserver 列表定義要通過(guò)怎樣的方法才能成為觀察者,UserListLogger 實(shí)現(xiàn) IObserver 接口。圖 4 的 UML 中展示了這些元素。圖 4. 可觀察的用戶(hù)列表和用戶(hù)列表事件日志程序如果在命令行中運(yùn)行它,您將看到以下輸出:% php observer.php 'Jack' added to user list%測(cè)試代碼創(chuàng)建 UserList,并將 UserListLogger 觀察者添加到其中。然后添加一個(gè)消費(fèi)者,并將這一更改通知 UserListLogger。認(rèn)識(shí)到 UserList 不知道日志程序?qū)?zhí)行什么操作很關(guān)鍵。可能存在一個(gè)或多個(gè)執(zhí)行其他操作的偵聽(tīng)程序。例如,您可能有一個(gè)向新用戶(hù)發(fā)送消息的觀察者,歡迎新用戶(hù)使用該系統(tǒng)。這種方法的價(jià)值在于 UserList 忽略所有依賴(lài)它的對(duì)象,它主要關(guān)注在列表更改時(shí)維護(hù)用戶(hù)列表并發(fā)送消息這一工作。此模式不限于內(nèi)存中的對(duì)象。它是在較大的應(yīng)用程序中使用的數(shù)據(jù)庫(kù)驅(qū)動(dòng)的消息查詢(xún)系統(tǒng)的基礎(chǔ)。
命令鏈模式命令鏈 模式以松散耦合主題為基礎(chǔ),發(fā)送消息、命令和請(qǐng)求,或通過(guò)一組處理程序發(fā)送任意內(nèi)容。每個(gè)處理程序都會(huì)自行判斷自己能否處理請(qǐng)求。如果可以,該請(qǐng)求被處理,進(jìn)程停止。您可以為系統(tǒng)添加或移除處理程序,而不影響其他處理程序。清單 5 顯示了此模式的一個(gè)示例。清單 5. Chain.php<?phpinterface ICommand{ function onCommand( $name, $args );}class CommandChain{ private $_commands = array(); public function addCommand( $cmd ) {$this->_commands []= $cmd; } public function runCommand( $name, $args ) {foreach( $this->_commands as $cmd ){ if ( $cmd->onCommand( $name, $args ) )return;} }}class UserCommand implements ICommand{ public function onCommand( $name, $args ) {if ( $name != 'addUser' ) return false;echo( 'UserCommand handling 'addUser'n' );return true; }}class MailCommand implements ICommand{ public function onCommand( $name, $args ) {if ( $name != 'mail' ) return false;echo( 'MailCommand handling 'mail'n' );return true; }}$cc = new CommandChain();$cc->addCommand( new UserCommand() );$cc->addCommand( new MailCommand() );$cc->runCommand( 'addUser', null );$cc->runCommand( 'mail', null );?>此代碼定義維護(hù) ICommand 對(duì)象列表的 CommandChain 類(lèi)。兩個(gè)類(lèi)都可以實(shí)現(xiàn) ICommand 接口 —— 一個(gè)對(duì)郵件的請(qǐng)求作出響應(yīng),另一個(gè)對(duì)添加用戶(hù)作出響應(yīng)。 圖 5 給出了 UML。圖 5. 命令鏈及其相關(guān)命令如果您運(yùn)行包含某些測(cè)試代碼的腳本,則會(huì)得到以下輸出:% php chain.php UserCommand handling 'addUser'MailCommand handling 'mail'%代碼首先創(chuàng)建 CommandChain 對(duì)象,并為它添加兩個(gè)命令對(duì)象的實(shí)例。然后運(yùn)行兩個(gè)命令以查看誰(shuí)對(duì)這些命令作出了響應(yīng)。如果命令的名稱(chēng)匹配 UserCommand 或 MailCommand,則代碼失敗,不發(fā)生任何操作。 為處理請(qǐng)求而創(chuàng)建可擴(kuò)展的架構(gòu)時(shí),命令鏈模式很有價(jià)值,使用它可以解決許多問(wèn)題。
策略模式我們講述的最后一個(gè)設(shè)計(jì)模式是策略 模式。在此模式中,算法是從復(fù)雜類(lèi)提取的,因而可以方便地替換。例如,如果要更改搜索引擎中排列頁(yè)的方法,則策略模式是一個(gè)不錯(cuò)的選擇。思考一下搜索引擎的幾個(gè)部分 —— 一部分遍歷頁(yè)面,一部分對(duì)每頁(yè)排列,另一部分基于排列的結(jié)果排序。在復(fù)雜的示例中,這些部分都在同一個(gè)類(lèi)中。通過(guò)使用策略模式,您可將排列部分放入另一個(gè)類(lèi)中,以便更改頁(yè)排列的方式,而不影響搜索引擎的其余代碼。作為一個(gè)較簡(jiǎn)單的示例,清單 6 顯示了一個(gè)用戶(hù)列表類(lèi),它提供了一個(gè)根據(jù)一組即插即用的策略查找一組用戶(hù)的方法。 清單 6. Strategy.php<?phpinterface IStrategy{ function filter( $record );}class FindAfterStrategy implements IStrategy{ private $_name; public function __construct( $name ) {$this->_name = $name; } public function filter( $record ) {return strcmp( $this->_name, $record ) <= 0; }}class RandomStrategy implements IStrategy{ public function filter( $record ) {return rand( 0, 1 ) >= 0.5; }}class UserList{ private $_list = array(); public function __construct( $names ) {if ( $names != null ){ foreach( $names as $name ) {$this->_list []= $name; }} } public function add( $name ) {$this->_list []= $name; } public function find( $filter ) {$recs = array();foreach( $this->_list as $user ){ if ( $filter->filter( $user ) )$recs []= $user;}return $recs; }}$ul = new UserList( array( 'Andy', 'Jack', 'Lori', 'Megan' ) );$f1 = $ul->find( new FindAfterStrategy( 'J' ) );print_r( $f1 );$f2 = $ul->find( new RandomStrategy() );print_r( $f2 );?>圖 6. 用戶(hù)列表和用于選擇用戶(hù)的策略UserList 類(lèi)是打包名稱(chēng)數(shù)組的一個(gè)包裝器。它實(shí)現(xiàn) find 方法,該方法利用幾個(gè)策略之一來(lái)選擇這些名稱(chēng)的子集。這些策略由 IStrategy 接口定義,該接口有兩個(gè)實(shí)現(xiàn):一個(gè)隨機(jī)選擇用戶(hù),另一個(gè)根據(jù)指定名稱(chēng)選擇其后的所有名稱(chēng)。運(yùn)行測(cè)試代碼時(shí),將得到以下輸出:% php strategy.php Array([0] => Jack[1] => Lori[2] => Megan)Array([0] => Andy[1] => Megan)%測(cè)試代碼為兩個(gè)策略運(yùn)行同一用戶(hù)列表,并顯示結(jié)果。在第一種情況中,策略查找排列在 J 后的任何名稱(chēng),所以您將得到 Jack、Lori 和 Megan。第二個(gè)策略隨機(jī)選取名稱(chēng),每次會(huì)產(chǎn)生不同的結(jié)果。在這種情況下,結(jié)果為 Andy 和 Megan。策略模式非常適合復(fù)雜數(shù)據(jù)管理系統(tǒng)或數(shù)據(jù)處理系統(tǒng),二者在數(shù)據(jù)篩選、搜索或處理的方式方面需要較高的靈活性。結(jié)束語(yǔ)本文介紹的僅僅是 PHP 應(yīng)用程序中使用的幾種最常見(jiàn)的設(shè)計(jì)模式。在設(shè)計(jì)模式 一書(shū)中演示了更多的設(shè)計(jì)模式。不要因架構(gòu)的神秘性而放棄。模式是一種絕妙的理念,適用于任何編程語(yǔ)言、任何技能水平。
