<接上一篇>
4、觀察者模式(Observer):
又叫發布訂閱模式,當一個主體對象發生改變時,依賴它的多個觀察者對象都得到通知並自動更新響應。就像報社一樣,今天發布的訊息只要是看這份報紙的人看到的都是同樣的內容。如果發布另一份報紙,也是一樣的。
好處:廣播式通訊,範圍大,一呼百應,便於操作一個組團,“公有制”。
弊端:不能單獨操作組團裡的個體,不能實行按需分配。
應用情境:操作多個對象,並操作相同。
代碼實現:
[php] view plaincopy
<?php /** * 優才網公開課範例程式碼 * * 觀察者模式 Observer * * @author 優才網全棧工程師教研組 * @see http://www.ucai.cn */ function output($string) { echo $string . "\n"; } //訂單資料對象簡單類比,這個是實際需要被觀察的對象(Subject),但是我們將其獨立,然後 //通過構造方法傳入到我們模式中的Subject中,這樣使具體業務更加獨立 class Order{ //訂單號 private $id = ''; //使用者ID private $userId = ''; //使用者名稱 private $userName = ''; //價格 private $price = ''; //下單時間 private $orderTime = ''; //訂單資料填充簡單類比,實際應用中可能會讀取使用者表單輸入並處理 public function __set($name, $value){ if (isset($this->$name)){ $this->$name = $value; } } //擷取訂單屬性 public function __get($name){ if (isset($this->$name)){ return $this->$name; } return ""; } } //假設的DB類,便於測試,實際會存入真實資料庫 class FakeDB{ public function save($data){ return true; } } class Client { public static function test() { //初始化一個訂單資料 $order = new Order(); $order->id = 1001; $order->userId = 9527; $order->userName = "God"; $order->price = 20.0; $order->orderTime = time(); //向資料庫儲存訂單 $db = new FakeDB(); $result = $db->save($order); if ($result){ //實際應用可能會寫到記錄檔中,這裡直接輸出 output( "[OrderId:{$order->id}] [UseId:{$order->userId}] [Price:{$order->price}]" ); //實際應用會調用郵件發送服務如sendmail,這裡直接輸出 output( "Dear {$order->userName}: Your order {$order->id} was confirmed!" ); //實際應用會調用郵件發送服務如sendmail,這裡直接輸出 output( "Dear Manager: User {$order->userName}(ID:{$order->userId}) submitted a new order {$order->id}, please handle it ASAP!" ); } } } Client::test();
<?php /** * 優才網公開課範例程式碼 * * 觀察者模式 Observer * * @author 優才網全棧工程師教研組 * @see http://www.ucai.cn */ function output($string) { echo $string . "\n"; } //訂單資料對象簡單類比,這個是實際需要被觀察的對象(Subject),但是我們將其獨立,然後 //通過構造方法傳入到我們模式中的Subject中,這樣使具體業務更加獨立 class Order{ //訂單號 private $id = ''; //使用者ID private $userId = ''; //使用者名稱 private $userName = ''; //價格 private $price = ''; //下單時間 private $orderTime = ''; //訂單資料填充簡單類比,實際應用中可能會讀取使用者表單輸入並處理 public function __set($name, $value){ if (isset($this->$name)){ $this->$name = $value; } } //擷取訂單屬性 public function __get($name){ if (isset($this->$name)){ return $this->$name; } return ""; } } //被觀察者, 負責維護觀察者並在變化發生是通知觀察者 class OrderSubject implements SplSubject { private $observers; private $order; public function __construct(Order $order) { $this->observers = new SplObjectStorage(); $this->order = $order; } //增加一個觀察者 public function attach(SplObserver $observer) { $this->observers->attach($observer); } //移除一個觀察者 public function detach(SplObserver $observer) { $this->observers->detach($observer); } //通知所有觀察者 public function notify() { foreach ($this->observers as $observer) { $observer->update($this); } } //返回主體對象的具體實現,供觀察者調用 public function getOrder() { return $this->order; } } //記錄業務資料日誌 (ActionLogObserver),實際可能還要抽象一層以處理不同的Action(業務操作),這裡省略 class ActionLogObserver implements SplObserver{ public function update(SplSubject $subject) { $order = $subject->getOrder(); //實際應用可能會寫到記錄檔中,這裡直接輸出 output( "[OrderId:{$order->id}] [UseId:{$order->userId}] [Price:{$order->price}]" ); } } //給使用者發送訂單確認郵件 (UserMailObserver) class UserMailObserver implements SplObserver{ public function update(SplSubject $subject) { $order = $subject->getOrder(); //實際應用會調用郵件發送服務如sendmail,這裡直接輸出 output( "Dear {$order->userName}: Your order {$order->id} was confirmed!" ); } } //給管理員發訂單處理通知訊息 (AdminMailObserver) class AdminMailObserver implements SplObserver{ public function update(SplSubject $subject) { $order = $subject->getOrder(); //實際應用會調用郵件發送服務如sendmail,這裡直接輸出 output( "Dear Manager: User {$order->userName}(ID:{$order->userId}) submitted a new order {$order->id}, please handle it ASAP!" ); } } //假設的DB類,便於測試,實際會存入真實資料庫 class FakeDB{ public function save($data){ return true; } } class Client { public static function test() { //初始化一個訂單資料 $order = new Order(); $order->id = 1001; $order->userId = 9527; $order->userName = "God"; $order->price = 20.0; $order->orderTime = time(); //綁定觀察者 $subject = new OrderSubject($order); $actionLogObserver = new ActionLogObserver(); $userMailObserver = new UserMailObserver(); $adminMailObserver = new AdminMailObserver(); $subject->attach($actionLogObserver); $subject->attach($userMailObserver); $subject->attach($adminMailObserver); //向資料庫儲存訂單 $db = new FakeDB(); $result = $db->save($order); if ($result){ //通知觀察者 $subject->notify(); } } } Client::test();
5、中介者模式(Mediator):
用中介對象封裝一系列的對象互動,中介使各對象不需要顯式地相互引用。類似於郵局,郵寄者和收件者不用自己跑很遠路,通過郵局就可以。
好處:簡化了對象之間的關係,減少子類的產生。
弊端:中介對象可能變得非常複雜,系統難以維護。
應用情境:不需要顯示地建立互動。
代碼實現:
[php] view plaincopy
<?php /** * 優才網公開課範例程式碼 * * 中介者模式 Mediator * * @author 優才網全棧工程師教研組 * @see http://www.ucai.cn */ function output($string) { echo $string . "\n"; } abstract class Mediator { // 中介者角色 abstract public function send($message,$colleague); } abstract class Colleague { // 抽象對象 private $_mediator = null; public function __construct($mediator) { $this->_mediator = $mediator; } public function send($message) { $this->_mediator->send($message,$this); } abstract public function notify($message); } class ConcreteMediator extends Mediator { // 具體中介者角色 private $_colleague1 = null; private $_colleague2 = null; public function send($message,$colleague) { if($colleague == $this->_colleague1) { $this->_colleague1->notify($message); } else { $this->_colleague2->notify($message); } } public function set($colleague1,$colleague2) { $this->_colleague1 = $colleague1; $this->_colleague2 = $colleague2; } } class Colleague1 extends Colleague { // 具體對象角色 public function notify($message) { output(sprintf('Colleague-1: %s', $message)); } } class Colleague2 extends Colleague { // 具體對象角色 public function notify($message) { output(sprintf('Colleague-2: %s', $message)); } } class Client { public static function test(){ // client $objMediator = new ConcreteMediator(); $objC1 = new Colleague1($objMediator); $objC2 = new Colleague2($objMediator); $objMediator->set($objC1,$objC2); $objC1->send("to c2 from c1"); $objC2->send("to c1 from c2"); } } Client::test();
6、狀態模式(State) :
對象在不同狀態下表現出不同的行為。就像女朋友一樣,高興了牽你的手,不高興了遛狗。在兩種狀態下變現出不同的行為。
好處:避免if語句實用,方便增加新狀態,封裝了狀態轉換規則。
弊端:增加系統類別和對象的數量。
應用情境:用於對象的不同功能的轉換。
代碼實現:
[php] view plaincopy
<?php /** * 優才網公開課範例程式碼 * * 狀態模式 State * * @author 優才網全棧工程師教研組 * @see http://www.ucai.cn */ function output($string) { echo $string . "\n"; } abstract class ILift { //電梯的四個狀態 const OPENING_STATE = 1; //門敞狀態 const CLOSING_STATE = 2; //門閉狀態 const RUNNING_STATE = 3; //運行狀態 const STOPPING_STATE = 4; //停止狀態; //設定電梯的狀態 public abstract function setState($state); //首先電梯門開啟動作 public abstract function open(); //電梯門有開啟,那當然也就有關閉了 public abstract function close(); //電梯要能上能下,跑起來 public abstract function run(); //電梯還要能停下來 public abstract function stop(); } /** * 電梯的實作類別 */ class Lift extends ILift { private $state; public function setState($state) { $this->state = $state; } //電梯門關閉 public function close() { //電梯在什麼狀態下才能關閉 switch ($this->state) { case ILift::OPENING_STATE: //如果是則可以關門,同時修改電梯狀態 $this->setState(ILift::CLOSING_STATE); break; case ILift::CLOSING_STATE: //如果電梯就是關門狀態,則什麼都不做 //do nothing; return ; break; case ILift::RUNNING_STATE: //如果是正在運行,門本來就是關閉的,也說明都不做 //do nothing; return ; break; case ILift::STOPPING_STATE: //如果是停止狀態,本也是關閉的,什麼也不做 //do nothing; return ; break; } output('Lift colse'); } //電梯門開啟 public function open() { //電梯在什麼狀態才能開啟 switch($this->state){ case ILift::OPENING_STATE: //如果已經在門敞狀態,則什麼都不做 //do nothing; return ; break; case ILift::CLOSING_STATE: //如是電梯時關閉狀態,則可以開啟 $this->setState(ILift::OPENING_STATE); break; case ILift::RUNNING_STATE: //執行狀態,則不能開門,什麼都不做 //do nothing; return ; break; case ILift::STOPPING_STATE: //停止狀態,淡然要開門了 $this->setState(ILift::OPENING_STATE); break; } output('Lift open'); } ///電梯開始跑起來 public function run() { switch($this->state){ case ILift::OPENING_STATE: //如果已經在門敞狀態,則不你能運行,什麼都不做 //do nothing; return ; break; case ILift::CLOSING_STATE: //如是電梯時關閉狀態,則可以運行 $this->setState(ILift::RUNNING_STATE); break; case ILift::RUNNING_STATE: //執行狀態,則什麼都不做 //do nothing; return ; break; case ILift::STOPPING_STATE: //停止狀態,可以運行 $this->setState(ILift::RUNNING_STATE); } output('Lift run'); } //電梯停止 public function stop() { switch($this->state){ case ILift::OPENING_STATE: //如果已經在門敞狀態,那肯定要先停下來的,什麼都不做 //do nothing; return ; break; case ILift::CLOSING_STATE: //如是電梯時關閉狀態,則當然可以停止了 $this->setState(ILift::CLOSING_STATE); break; case ILift::RUNNING_STATE: //執行狀態,有運行當然那也就有停止了 $this->setState(ILift::CLOSING_STATE); break; case ILift::STOPPING_STATE: //停止狀態,什麼都不做 //do nothing; return ; break; } output('Lift stop'); } } class Client { public static function test() { $lift = new Lift(); //電梯的初始條件應該是停止狀態 $lift->setState(ILift::STOPPING_STATE); //首先是電梯門開啟,人進去 $lift->open(); //然後電梯門關閉 $lift->close(); //再然後,電梯跑起來,向上或者向下 $lift->run(); //最後到達目的地,電梯挺下來 $lift->stop(); } } Client::test();
<?php /** * 優才網公開課範例程式碼 * * 狀態模式 State * * @author 優才網全棧工程師教研組 * @see http://www.ucai.cn */ function output($string) { echo $string . "\n"; } /** * * 定義一個電梯的介面 */ abstract class LiftState{ //定義一個環境角色,也就是封裝狀態的變換引起的功能變化 protected $_context; public function setContext(Context $context){ $this->_context = $context; } //首先電梯門開啟動作 public abstract function open(); //電梯門有開啟,那當然也就有關閉了 public abstract function close(); //電梯要能上能下,跑起來 public abstract function run(); //電梯還要能停下來,停不下來那就扯淡了 public abstract function stop(); } /** * 環境類:定義客戶感興趣的介面。維護一個ConcreteState子類的執行個體,這個執行個體定義目前狀態。 */ class Context { //定義出所有的電梯狀態 static $openningState = null; static $closeingState = null; static $runningState = null; static $stoppingState = null; public function __construct() { self::$openningState = new OpenningState(); self::$closeingState = new ClosingState(); self::$runningState = new RunningState(); self::$stoppingState = new StoppingState(); } //定一個當前電梯狀態 private $_liftState; public function getLiftState() { return $this->_liftState; } public function setLiftState($liftState) { $this->_liftState = $liftState; //把當前的環境通知到各個實作類別中 $this->_liftState->setContext($this); } public function open(){ $this->_liftState->open(); } public function close(){ $this->_liftState->close(); } public function run(){ $this->_liftState->run(); } public function stop(){ $this->_liftState->stop(); } } /** * 在電梯門開啟的狀態下能做什麼事情 */ class OpenningState extends LiftState { /** * 開啟當然可以關閉了,我就想測試一下電梯門開關功能 * */ public function close() { //狀態修改 $this->_context->setLiftState(Context::$closeingState); //動作委託為CloseState來執行 $this->_context->getLiftState()->close(); } //開啟電梯門 public function open() { output('lift open...'); } //門開著電梯就想跑,這電梯,嚇死你! public function run() { //do nothing; } //開門還不停止? public function stop() { //do nothing; } } /** * 電梯門關閉以後,電梯可以做哪些事情 */ class ClosingState extends LiftState { //電梯門關閉,這是關閉狀態要實現的動作 public function close() { output('lift close...'); } //電梯門關了再開啟,逗你玩呢,那這個允許呀 public function open() { $this->_context->setLiftState(Context::$openningState); //置為門敞狀態 $this->_context->getLiftState()->open(); } //電梯門關了就跑,這是再正常不過了 public function run() { $this->_context->setLiftState(Context::$runningState); //設定為運行狀態; $this->_context->getLiftState()->run(); } //電梯門關著,我就不按樓層 public function stop() { $this->_context->setLiftState(Context::$stoppingState); //設定為停止狀態; $this->_context->getLiftState()->stop(); } } /** * 電梯在運行狀態下能做哪些動作 */ class RunningState extends LiftState { //電梯門關閉?這是肯定了 public function close() { //do nothing } //啟動並執行時候開電梯門?你瘋了!電梯不會給你開的 public function open() { //do nothing } //這是在運行狀態下要實現的方法 public function run() { output('lift run...'); } //這個事絕對是合理的,光運行不停止還有誰敢做這個電梯?!估計只有上帝了 public function stop() { $this->_context->setLiftState(Context::$stoppingState); //環境設定為停止狀態; $this->_context->getLiftState()->stop(); } } /** * 在停止狀態下能做什麼事情 */ class StoppingState extends LiftState { //停止狀態關門?電梯門本來就是關著的! public function close() { //do nothing; } //停止狀態,開門,那是要的! public function open() { $this->_context->setLiftState(Context::$openningState); $this->_context->getLiftState()->open(); } //停止狀態再跑起來,正常的很 public function run() { $this->_context->setLiftState(Context::$runningState); $this->_context->getLiftState()->run(); } //停止狀態是怎麼發生的呢?當然是停止方法執行了 public function stop() { output('lift stop...'); } } /** * 類比電梯的動作 */ class Client { public static function test() { $context = new Context(); $context->setLiftState(new ClosingState()); $context->open(); $context->close(); $context->run(); $context->stop(); } } Client::test();