隔間牆有耳的觀察者模式(Observer Patern)

巴扎黑
發布: 2016-11-12 13:56:28
原創
1442 人瀏覽過

登入系統想必大家都做過,驗證用戶名密碼就登入成功,日誌系統應該記錄此次登錄,如果登入出錯,安全系統應該會記錄這次錯誤,郵件系統也應該會發送相關郵件給管理員,等等。這就好像登入系統被許多人監視一樣,一旦有什麼風吹草動,立即會被其它系統獲悉。那就用觀察者模式來試試,類別圖如下:

隔間牆有耳的觀察者模式(Observer Patern)
很簡單的模式,實作程式碼:

Php程式碼  

<?php  
interface Observable{  
    function attach( Observer $observer );  
    function detach( Observer $observer );  
    function notify();  
}  
  
  
class login implements Observable{  
    const LOGIN_USER_UNKNOW = 1;  
    const LOGIN_WRONG_PASS = 2;  
    const LOGIN_ACCESS = 3;  
    private $status = array();  
    private $observers = array();  
  
    public function setStatus( $status, $user, $ip ) {  
        $this->status = array( $status, $user, $ip );  
    }  
    public function getStatus() {  
        return $this->status;  
    }  
    public function handleLogin( $user, $pass, $ip ) {  
        switch ( mt_rand( 1, 3 ) ) {  
        case 1:  
            $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip );  
            $ret = false;  
            break;  
        case 2:  
            $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip );  
            $ret = false;  
            break;  
        case 3:  
            $this->setStatus( self::LOGIN_ACCESS, $user, $ip );  
            $ret = true;  
            break;  
        }  
        $this->notify();  
        return $ret;  
    }  
  
  
    public function attach( Observer $observer ) {  
        $this->observers[] = $observer;  
    }  
  
    public function detach( Observer $observer ) {  
        $newObservers = array();  
        foreach ( $this->observers as $obs ) {  
            if ( $obs !== $observer )  
                $newObservers[] = $obs;  
        }  
        $this->observers = $newObservers;  
    }  
  
    public function notify() {  
        foreach ( $this->observers as $obs ) {  
            $obs->update( $this );  
        }  
    }  
}  
  
interface Observer{  
    function update( Observable $observable );  
}  
  
class SecurityMonitor implements Observer{  
    function update( Observable $observable ) {  
        $status = $observable->getStatus();  
        if($status[0] == Login::LOGIN_WRONG_PASS){  
            echo __CLASS__.":".$status[1]."于".$status[2]."登录失败";  
        }  
    }  
}  
  
$login = new Login();  
$login->attach(new SecurityMonitor());  
$login->handleLogin(&#39;XXX&#39;,&#39;XXX&#39;,&#39;127.0.0.1&#39;);  
?>
登入後複製

 

於70. 0.1登入失敗[Finished in 0.1s]  

程式碼中可以看到login物件主動加入SecurityMonitor物件觀察。這樣要呼叫Login::getStatus(),SecurityMonitor類別就必須了解更多資訊。雖然呼叫發生於一個ObServable物件上,但無法保證物件也是一個Login物件。為解決這個問題,有一個方法:斷斷續續保持ObServable介面的通用性,由ObServer類別負責保證它們的主體是正確的類型。它們甚至能將自己添加到主體上。類別圖如下:

實作程式碼如下:隔間牆有耳的觀察者模式(Observer Patern)
Php程式碼  

<?php  
interface Observable{  
    function attach( Observer $observer );  
    function detach( Observer $observer );  
    function notify();  
}  
  
  
class login implements Observable{  
    const LOGIN_USER_UNKNOW = 1;  
    const LOGIN_WRONG_PASS = 2;  
    const LOGIN_ACCESS = 3;  
    private $status = array();  
    private $observers = array();  
  
    public function setStatus( $status, $user, $ip ) {  
        $this->status = array( $status, $user, $ip );  
    }  
    public function getStatus() {  
        return $this->status;  
    }  
    public function handleLogin( $user, $pass, $ip ) {  
        switch ( mt_rand( 1, 3 ) ) {  
        case 1:  
            $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip );  
            $ret = false;  
            break;  
        case 2:  
            $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip );  
            $ret = false;  
            break;  
        case 3:  
            $this->setStatus( self::LOGIN_ACCESS, $user, $ip );  
            $ret = true;  
            break;  
        }  
        $this->notify();  
        return $ret;  
    }  
  
  
    public function attach( Observer $observer ) {  
        $this->observers[] = $observer;  
    }  
  
    public function detach( Observer $observer ) {  
        $newObservers = array();  
        foreach ( $this->observers as $obs ) {  
            if ( $obs !== $observer )  
                $newObservers[] = $obs;  
        }  
        $this->observers = $newObservers;  
    }  
  
    public function notify() {  
        foreach ( $this->observers as $obs ) {  
            $obs->update( $this );  
        }  
    }  
}  
  
interface Observer{  
    function update( Observable $observable );  
}  
//以上代码和上例是一样的  
abstract class LoginObserver implements Observer{  
    private $login;  
    public function __construct( Login $login ) {  
        $this->login = $login;  
        $login->attach( $this );  
    }  
  
    public function update( Observable $observable ) {  
        if ( $this->login === $observable )  
            $this->doUpdate( $observable );  
    }  
    abstract function doUpdate( Login $login );  
}  
  
class SecurityMonitor extends LoginObserver{  
    public function doUpdate( Login $login ) {  
        $status = $login->getStatus();  
        if ( $status[0] == Login::LOGIN_WRONG_PASS )  
            echo __CLASS__.":".$status[1]."于".$status[2]."登录失败";  
    }  
}  
  
$login = new Login();  
new SecurityMonitor($login);//<strong>此外login对象是被动被观察的</strong>  
$login->handleLogin( &#39;XXX&#39;, &#39;XXX&#39;, &#39;127.0.0.1&#39; );  
?>
登入後複製

運作結果與上範例相同  

在php5後,內建的SPL擴充提供了對觀察者模式的原生支援。將上例透過SPL改進後:

Php程式碼  

<?php  
class login implements SplSubject{  
    const LOGIN_USER_UNKNOW = 1;  
    const LOGIN_WRONG_PASS = 2;  
    const LOGIN_ACCESS = 3;  
    private $status = array();  
    // private $observers = array();  
    private $storage;  
    public function __construct() {  
        $this->storage = new SplObjectStorage();  
    }  
    public function setStatus( $status, $user, $ip ) {  
        $this->status = array( $status, $user, $ip );  
    }  
    public function getStatus() {  
        return $this->status;  
    }  
    public function handleLogin( $user, $pass, $ip ) {  
        switch ( mt_rand( 1, 3 ) ) {  
        case 1:  
            $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip );  
            $ret = false;  
            break;  
        case 2:  
            $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip );  
            $ret = false;  
            break;  
        case 3:  
            $this->setStatus( self::LOGIN_ACCESS, $user, $ip );  
            $ret = true;  
            break;  
        }  
        $this->notify();  
        return $ret;  
    }  
  
  
    public function attach( SplObserver $observer ) {  
        $this->storage->attach( $observer );  
    }  
  
    public function detach( SplObserver $observer ) {  
        $this->storage->detach( $observer );  
    }  
  
    public function notify() {  
        foreach ( $this->storage as $obs ) {  
            $obs->update( $this );  
        }  
    }  
}  
  
abstract class LoginObserver implements SplObserver{  
    private $login;  
    public function __construct( Login $login ) {  
        $this->login = $login;  
        $login->attach( $this );  
    }  
  
    public function update( SplSubject $subject ) {  
        if ( $this->login === $subject )  
            $this->doUpdate( $subject );  
    }  
    abstract function doUpdate( Login $login );  
}  
  
class SecurityMonitor extends LoginObserver{  
    public function doUpdate( Login $login ) {  
        $status = $login->getStatus();  
        if ( $status[0] == Login::LOGIN_WRONG_PASS )  
            echo __CLASS__.":".$status[1]."于".$status[2]."登录失败";  
    }  
}  
  
$login = new Login();  
new SecurityMonitor( $login );  
$login->handleLogin( &#39;XXX&#39;, &#39;XXX&#39;, &#39;127.0.0.1&#39; );  
?>
登入後複製

程式碼都寫完了,還是要懂點理論的。

觀察者模式的定義

定義物件間一種一對多的依賴關係,使得每當一個物件改變狀態,則所有依賴它的物件都會被通知並被自動更新。觀察者模式由四種角色構成:

1、Subject被觀察者

定義被觀察者必須實現的職責,它必須能夠動態地增加、取消觀察者。它一般是抽象類別或實現類,僅僅完成作為被觀察者必須實現的職責,管理觀察者並通過觀察者。

2、Observer觀察者

觀察者接收到訊息後,即進行update操作,對接收到的資訊進行處理。

3、ConcreteSubject具體的被觀察者

定義被觀察者自己的業務邏輯,同時定義對哪些事件進行通知。

4、ConcreteObserver具體的觀察者

每個觀察在接收到訊息後的處理反應是不同,各個觀察者有自己的處理邏輯。

觀察者模式的優點

1、觀察者和被觀察者之間是抽象耦合

如此設計,則不管是增加觀察者還是被觀察者都非常容易擴展,而且在java、php中都已經實現的抽象層級的定義,在系統擴展方面更是得心應手。

2、建立一套觸發機制

根據單一職責原則,每個類別的職責是單一的,那麼怎麼把各個單一的職責串聯成真實世界的複雜的邏輯關係呢?觀察者模式可以完美地實現這裡的鏈條形式

 

觀察者模式的缺點

觀察者模式需要考慮開發效率和運行效率的問題,一個被觀察者,多個觀察者,開發和調試就會比較複雜,而且在php中訊息的通知是順序執行,一個觀察者卡殼,會影響整體的執行效率。在這種情況下,一般會多考慮異步的方式。多級觸發時的效率更是讓人擔憂,設計時會注意。

 

 

觀察者模式的使用情境

1、關聯行為場景。要注意的是,關係行為是可分割的,而不是「組合」關係

2、事件多層次觸發場景

3、跨系統的訊息交換場景,如訊息佇列的處理機制

相關標籤:
php
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板