更好的構造開發(fā)模板 五種PHP設計模式

如上所述,有時此類模式在規(guī)模較小的環(huán)境中似乎有些大材小用。不過,最好還是學習這種扎實的編碼形式,以便應用于任意規(guī)模的項目中。
單元素模式某些應用程序資源是獨占的,因為有且只有一個此類型的資源。例如,通過數(shù)據(jù)庫句柄到數(shù)據(jù)庫的連接是獨占的。您希望在應用程序中共享數(shù)據(jù)庫句柄,因為在保持連接打開或關閉時,它是一種開銷,在獲取單個頁面的過程中更是如此。單元素模式可以滿足此要求。如果應用程序每次包含且僅包含一個對象,那么這個對象就是一個單元素(Singleton)。清單 3 中的代碼顯示了 PHP V5 中的一個數(shù)據(jù)庫連接單元素。清單 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 的單個類。您不能創(chuàng)建自已的 DatabaseConnection,因為構造函數(shù)是專用的。但使用靜態(tài) get 方法,您可以獲得且僅獲得一個 DatabaseConnection 對象。此代碼的 UML 如圖 3 所示。圖 3. 數(shù)據(jù)庫連接單元素在兩次調(diào)用間,handle 方法返回的數(shù)據(jù)庫句柄是相同的,這就是最好的證明。您可以在命令行中運行代碼來觀察這一點。% php singleton.php Handle = Object id #3Handle = Object id #3%返回的兩個句柄是同一對象。如果您在整個應用程序中使用數(shù)據(jù)庫連接單元素,那么就可以在任何地方重用同一句柄。您可以使用全局變量存儲數(shù)據(jù)庫句柄,但是,該方法僅適用于較小的應用程序。在較大的應用程序中,應避免使用全局變量,并使用對象和方法訪問資源。
觀察者模式觀察者模式為您提供了避免組件之間緊密耦合的另一種方法。該模式非常簡單:一個對象通過添加一個方法(該方法允許另一個對象,即觀察者 注冊自己)使本身變得可觀察。當可觀察的對象更改時,它會將消息發(fā)送到已注冊的觀察者。這些觀察者使用該信息執(zhí)行的操作與可觀察的對象無關。結果是對象可以相互對話,而不必了解原因。 一個簡單示例是系統(tǒng)中的用戶列表。清單 4 中的代碼顯示一個用戶列表,添加用戶時,它將發(fā)送出一條消息。添加用戶時,通過發(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' );?>此代碼定義四個元素:兩個接口和兩個類。IObservable 接口定義可以被觀察的對象,UserList 實現(xiàn)該接口,以便將本身注冊為可觀察。IObserver 列表定義要通過怎樣的方法才能成為觀察者,UserListLogger 實現(xiàn) IObserver 接口。圖 4 的 UML 中展示了這些元素。圖 4. 可觀察的用戶列表和用戶列表事件日志程序如果在命令行中運行它,您將看到以下輸出:% php observer.php 'Jack' added to user list%測試代碼創(chuàng)建 UserList,并將 UserListLogger 觀察者添加到其中。然后添加一個消費者,并將這一更改通知 UserListLogger。認識到 UserList 不知道日志程序將執(zhí)行什么操作很關鍵。可能存在一個或多個執(zhí)行其他操作的偵聽程序。例如,您可能有一個向新用戶發(fā)送消息的觀察者,歡迎新用戶使用該系統(tǒng)。這種方法的價值在于 UserList 忽略所有依賴它的對象,它主要關注在列表更改時維護用戶列表并發(fā)送消息這一工作。此模式不限于內(nèi)存中的對象。它是在較大的應用程序中使用的數(shù)據(jù)庫驅動的消息查詢系統(tǒng)的基礎。
命令鏈模式命令鏈 模式以松散耦合主題為基礎,發(fā)送消息、命令和請求,或通過一組處理程序發(fā)送任意內(nèi)容。每個處理程序都會自行判斷自己能否處理請求。如果可以,該請求被處理,進程停止。您可以為系統(tǒng)添加或移除處理程序,而不影響其他處理程序。清單 5 顯示了此模式的一個示例。清單 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 );?>此代碼定義維護 ICommand 對象列表的 CommandChain 類。兩個類都可以實現(xiàn) ICommand 接口 —— 一個對郵件的請求作出響應,另一個對添加用戶作出響應。 圖 5 給出了 UML。圖 5. 命令鏈及其相關命令如果您運行包含某些測試代碼的腳本,則會得到以下輸出:% php chain.php UserCommand handling 'addUser'MailCommand handling 'mail'%代碼首先創(chuàng)建 CommandChain 對象,并為它添加兩個命令對象的實例。然后運行兩個命令以查看誰對這些命令作出了響應。如果命令的名稱匹配 UserCommand 或 MailCommand,則代碼失敗,不發(fā)生任何操作。 為處理請求而創(chuàng)建可擴展的架構時,命令鏈模式很有價值,使用它可以解決許多問題。
策略模式我們講述的最后一個設計模式是策略 模式。在此模式中,算法是從復雜類提取的,因而可以方便地替換。例如,如果要更改搜索引擎中排列頁的方法,則策略模式是一個不錯的選擇。思考一下搜索引擎的幾個部分 —— 一部分遍歷頁面,一部分對每頁排列,另一部分基于排列的結果排序。在復雜的示例中,這些部分都在同一個類中。通過使用策略模式,您可將排列部分放入另一個類中,以便更改頁排列的方式,而不影響搜索引擎的其余代碼。作為一個較簡單的示例,清單 6 顯示了一個用戶列表類,它提供了一個根據(jù)一組即插即用的策略查找一組用戶的方法。 清單 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. 用戶列表和用于選擇用戶的策略UserList 類是打包名稱數(shù)組的一個包裝器。它實現(xiàn) find 方法,該方法利用幾個策略之一來選擇這些名稱的子集。這些策略由 IStrategy 接口定義,該接口有兩個實現(xiàn):一個隨機選擇用戶,另一個根據(jù)指定名稱選擇其后的所有名稱。運行測試代碼時,將得到以下輸出:% php strategy.php Array([0] => Jack[1] => Lori[2] => Megan)Array([0] => Andy[1] => Megan)%測試代碼為兩個策略運行同一用戶列表,并顯示結果。在第一種情況中,策略查找排列在 J 后的任何名稱,所以您將得到 Jack、Lori 和 Megan。第二個策略隨機選取名稱,每次會產(chǎn)生不同的結果。在這種情況下,結果為 Andy 和 Megan。策略模式非常適合復雜數(shù)據(jù)管理系統(tǒng)或數(shù)據(jù)處理系統(tǒng),二者在數(shù)據(jù)篩選、搜索或處理的方式方面需要較高的靈活性。結束語本文介紹的僅僅是 PHP 應用程序中使用的幾種最常見的設計模式。在設計模式 一書中演示了更多的設計模式。不要因架構的神秘性而放棄。模式是一種絕妙的理念,適用于任何編程語言、任何技能水平。
