最初在設計模式 一書中,許多設計模式都鼓勵使用鬆散耦合。要理解這個概念,讓咱們最好談一下許多開發人員從事大型系統的艱苦歷程。在更改一個代碼片斷時,就會發生問題,系統其餘部分 —— 您曾認爲徹底不相關的部分中也有可能出現級聯破壞。該問題在於緊密耦合 。系統某個部分中的函數和類嚴重依賴於系統的其餘部分中函數和類的行爲和結構。您須要一組模式,使這些類可以相互通訊,但不但願將它們緊密綁定在一塊兒,以免出現聯鎖。在大型系統中,許多代碼依賴於少數幾個關鍵類。須要更改這些類時,可能會出現困難。例如,假設您有一個從文件讀取的 User
類。您但願將其更改成從數據庫讀取的其餘類,可是,全部的代碼都引用從文件讀取的原始類。這時候,使用工廠模式會很方便。工廠模式 是一種類,它具備爲您建立對象的某些方法。您可使用工廠類建立對象,而不直接使用 new
。這樣,若是您想要更改所建立的對象類型,只需更改該工廠便可。使用該工廠的全部代碼會自動更改。清單 1 顯示工廠類的一個示列。等式的服務器端包括兩個部分:數據庫和一組 PHP 頁面,這些頁面容許您添加反饋、請求反饋列表並獲取與特定反饋相關的文章。php
清單1:Factory1.phpmysql
1 <?php 2 interface IUser 3 { 4 function getName(); 5 } 6 class User implements IUser 7 { 8 public function __construct($id){} 9 10 public function getName() 11 { 12 return "Jack"; 13 } 14 } 15 class UserFactory 16 { 17 public static function Create($id) 18 { 19 return new User($id); 20 } 21 } 22 $uo = UserFactory::Create(1); 23 echo($uo->getName()."\n");
清單1的UML表示算法
測試代碼會向工廠請求 User
對象,並輸出 getName
方法的結果。sql
有一種工廠模式的變體使用工廠方法。類中的這些公共靜態方法構造該類型的對象。若是建立此類型的對象很是重要,此方法很是有用。例如,假設您須要先建立對象,而後設置許多屬性。此版本的工廠模式會將該進程封裝在單個位置中,這樣,不用複製複雜的初始化代碼,也沒必要將複製好的代碼在在代碼庫中處處粘貼。數據庫
清單2:Factory2.php設計模式
1 <?php 2 interface IUser 3 { 4 function getName(); 5 } 6 7 class User implements IUser 8 { 9 public static function Load($id) 10 { 11 return new User($id); 12 } 13 public static function Create() 14 { 15 return new User(null); 16 } 17 public function __construct($id){} 18 public function getName() 19 { 20 return "Jack2"; 21 } 22 } 23 $uo = User::Load(1); 24 echo($uo->getName()."\n");
清單2的RML表示服務器
某些應用程序資源是獨佔的,由於有且只有一個此類型的資源。例如,經過數據庫句柄到數據庫的鏈接是獨佔的。您但願在應用程序中共享數據庫句柄,由於在保持鏈接打開或關閉時,它是一種開銷,在獲取單個頁面的過程當中更是如此。架構
單元素模式能夠知足此要求。若是應用程序每次包含且僅包含一個對象,那麼這個對象就是一個單元素(Singleton)。清單 3 中的代碼顯示了 PHP V5 中的一個數據庫鏈接單元素。併發
清單3:Singleton.phpdom
1 <?php 2 require_once 'DB.php'; 3 4 class DatabaseConnection 5 { 6 public static function get() 7 { 8 static $db = null; 9 if ($db == null) 10 $db = new DatabaseConnection(); 11 return $db; 12 } 13 14 private $_handle = null; 15 16 private function __construct() 17 { 18 $dsn = 'mysql://root:password@localhost/photos'; 19 $this->_handle =&DB::Connect( $dsn, array() ); 20 } 21 public function handle() 22 { 23 return $this->_handle; 24 } 25 } 26 print("Handle = ".DatabaseConnection::get()->handle()."\n"); 27 print("Handle = ".DatabaseConnection::get()->handle()."\n");
清單3的UML表示
返回的兩個句柄是同一對象。若是您在整個應用程序中使用數據庫鏈接單元素,那麼就能夠在任何地方重用同一句柄。
您可使用全局變量存儲數據庫句柄,可是,該方法僅適用於較小的應用程序。在較大的應用程序中,應避免使用全局變量,並使用對象和方法訪問資源。
觀察者模式爲您提供了避免組件之間緊密耦合的另外一種方法。該模式很是簡單:一個對象經過添加一個方法(該方法容許另外一個對象,即觀察者 註冊本身)使自己變得可觀察。當可觀察的對象更改時,它會將消息發送到已註冊的觀察者。這些觀察者使用該信息執行的操做與可觀察的對象無關。結果是對象能夠相互對話,而沒必要了解緣由。
一個簡單示例是系統中的用戶列表。清單 4 中的代碼顯示一個用戶列表,添加用戶時,它將發送出一條消息。添加用戶時,經過發送消息的日誌觀察者能夠觀察此列表。
清單4:Observer.php
1 <?php 2 interface IObserver 3 { 4 function onChanged($sender,$args); 5 } 6 interface IObservable 7 { 8 function addObserver($observer); 9 } 10 class UserList implements IObservable 11 { 12 private $_observers = array(); 13 public function addCustomer($name) 14 { 15 foreach($this->_observers as $obs) 16 $obs->onChanged($this,$name); 17 } 18 public function addObserver($observer) 19 { 20 $this->_observers[] = $observer; 21 } 22 } 23 class UserListLogger implements IObserver 24 { 25 public function onChanged($sender,$args) 26 { 27 echo("$args added to user list\n"); 28 } 29 } 30 $ul = new UserList(); 31 $ul->addObserver(new UserListLogger()); 32 $ul->addCustomer("Jack");
清單4的UML表示
測試代碼建立 UserList
,並將 UserListLogger
觀察者添加到其中。而後添加一個消費者,並將這一更改通知 UserListLogger
。
認識到 UserList
不知道日誌程序將執行什麼操做很關鍵。可能存在一個或多個執行其餘操做的偵聽程序。例如,您可能有一個向新用戶發送消息的觀察者,歡迎新用戶使用該系統。這種方法的價值在於 UserList
忽略全部依賴它的對象,它主要關注在列表更改時維護用戶列表併發送消息這一工做。
此模式不限於內存中的對象。它是在較大的應用程序中使用的數據庫驅動的消息查詢系統的基礎。
命令鏈模式以鬆散耦合主題爲基礎,發送消息、命令和請求,或經過一組處理程序發送任意內容。每一個處理程序都會自行判斷本身可否處理請求。若是能夠,該請求被處理,進程中止。您能夠爲系統添加或移除處理程序,而不影響其餘處理程序。清單 5 顯示了此模式的一個示例。
清單5:Chain.php
1 <?php 2 interface ICommand 3 { 4 function onCommand($name,$args); 5 } 6 class CommandChain 7 { 8 private $_commands = array(); 9 public function addCommand($cmd) 10 { 11 $this->_commands[] = $cmd; 12 } 13 public function runCommand($name,$args) 14 { 15 foreach($this->_commands as $cmd) 16 { 17 if ($cmd->onCommand($name,$args)) 18 return; 19 } 20 } 21 } 22 class UserCommand implements ICommand 23 { 24 public function onCommand($name,$args) 25 { 26 if ($name != 'addUser') return false; 27 echo("UserCommand handling 'addUser'\n"); 28 return true; 29 } 30 } 31 class MailCommand implements ICommand 32 { 33 public function onCommand($name,$args) 34 { 35 if ($name != 'mail') return false; 36 echo("MailCommand handling 'mail'\n"); 37 return true; 38 } 39 } 40 41 $cc = new CommandChain(); 42 $cc->addCommand(new UserCommand()); 43 $cc->addCommand(new MailCommand()); 44 $cc->runCommand('addUser', null); 45 $cc->runCommand('mail', null);
清單5的UML表示
代碼首先建立 CommandChain
對象,併爲它添加兩個命令對象的實例。而後運行兩個命令以查看誰對這些命令做出了響應。若是命令的名稱匹配 UserCommand
或 MailCommand
,則代碼失敗,不發生任何操做。
爲處理請求而建立可擴展的架構時,命令鏈模式頗有價值,使用它能夠解決許多問題。
咱們講述的最後一個設計模式是策略 模式。在此模式中,算法是從複雜類提取的,於是能夠方便地替換。例如,若是要更改搜索引擎中排列頁的方法,則策略模式是一個不錯的選擇。思考一下搜索引擎的幾個部分 —— 一部分遍歷頁面,一部分對每頁排列,另外一部分基於排列的結果排序。在複雜的示例中,這些部分都在同一個類中。經過使用策略模式,您可將排列部分放入另外一個類中,以便更改頁排列的方式,而不影響搜索引擎的其他代碼。
做爲一個較簡單的示例,清單 6 顯示了一個用戶列表類,它提供了一個根據一組即插即用的策略查找一組用戶的方法。
清單6:Strategy.php
1 <?php 2 interface IStrategy 3 { 4 function filter($record); 5 } 6 class FindAfterStrategy implements IStrategy 7 { 8 private $_name; 9 public function __construct($name) 10 { 11 $this->_name = $name; 12 } 13 public function filter($record) 14 { 15 return strcmp($this->_name,$record) <= 0; 16 } 17 } 18 class RandomStrategy implements IStrategy 19 { 20 public function filter($record) 21 { 22 return rand(0,1) >= 0.5; 23 } 24 } 25 class UserList 26 { 27 private $_list = array(); 28 public function __construct($names) 29 { 30 if ($names != null) 31 { 32 foreach($names as $name) 33 $this->_list[] = $name; 34 } 35 } 36 public function add($name) 37 { 38 $this->_list[] = $name; 39 } 40 public function find($filter) 41 { 42 $recs = array(); 43 foreach($this->_list as $user) 44 { 45 if ($filter->filter($user)) 46 $recs[] = $user; 47 } 48 return $recs; 49 } 50 } 51 52 $ul = new UserList(array("Andy","Jack","Lori","Megan")); 53 $f1 = $ul->find(new FindAfterStrategy("J")); 54 print_r($f1); 55 $f2 = $ul->find(new RandomStrategy()); 56 print_r($f2);
清單6的UML表示
測試代碼爲兩個策略運行同一用戶列表,並顯示結果。在第一種狀況中,策略查找排列在 J
後的任何名稱,因此您將獲得 Jack、Lori 和 Megan。第二個策略隨機選取名稱,每次會產生不一樣的結果。在這種狀況下,結果爲 Andy 和 Megan。
策略模式很是適合複雜數據管理系統或數據處理系統,兩者在數據篩選、搜索或處理的方式方面須要較高的靈活性。