Maison  >  Article  >  développement back-end  >  隔墙有耳的观察者模式(Observer Patern)

隔墙有耳的观察者模式(Observer Patern)

巴扎黑
巴扎黑original
2016-11-12 13:56:281451parcourir

登录系统想必大家都做过,验证用户名密码就登录成功,日志系统应该记录此次登录,如果登录出错,安全系统应该会记录此次错误,邮件系统也应该会发送相关邮件给管理员,等等。这就好像登录系统被很多人监视一样,一旦有什么风吹草动,立即会被其它系统获悉。那就用观察者模式来试试,类图如下:

65b51b04-bb57-3437-99c7-c273ef01c239.png
很简单的模式,实现代码:

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;);  
?>

 

出错时的运行结果:  

SecurityMonitor:XXX于127.0.0.1登录失败[Finished in 0.1s]  

代码中可以看到login对象主动添加SecurityMonitor对象观察。这样要调用Login::getStatus(),SecurityMonitor类就必须了解更多信息。虽然调用发生于一个ObServable对象上,但无法保证对象也是一个Login对象。为解决这个问题,有一个办法:断续保持ObServable接口的通用性,由ObServer类负责保证它们的主体是正确的类型。它们甚至能将自己添加到主体上。类图如下:

723cccb4-db2c-302b-aa9e-532992a927ed.png
实现代码如下:

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、跨系统的消息交换场景,如消息队列的处理机制


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn