属垣有耳的观察者模式(Observer Patern)

属垣有耳的观察者模式(Observer Patern)

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

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


属垣有耳的观察者模式(Observer Patern)
很简单的模式,实现代码:

<?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('XXX','XXX','127.0.0.1');
?>
出错时的运行结果:
SecurityMonitor:XXX于127.0.0.1登录失败[Finished in 0.1s]

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


属垣有耳的观察者模式(Observer Patern)
实现代码如下:

<?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);//此外login对象是被动被观察的
$login->handleLogin( 'XXX', 'XXX', '127.0.0.1' );
?>
运行结果与上例子相同

在php5后,内置的SPL扩展提供了对观察者模式的原生支持。将上例子通过SPL改进后:

<?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( 'XXX', 'XXX', '127.0.0.1' );
?>

 

 

 

代码都写完了,还是要懂点理论的。

观察者模式的定义

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。观察者模式由四种角色构成:

1、Subject被观察者

定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责,管理观察者并通过观察者。

2、Observer观察者

观察者接收到消息后,即进行update操作,对接收到的信息进行处理。

3、ConcreteSubject具体的被观察者

定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。

4、ConcreteObserver具体的观察者

每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。

 

 

观察者模式的优点

1、观察者和被观察者之间是抽象耦合

如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在java、php中都已经实现的抽象层级的定义,在系统扩展方面更是得心应手。

2、建立一套触发机制

根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑关系呢?观察者模式可以完美地实现这里的链条形式

 

 

 

观察者模式的缺点

观察者模式需要考虑一下开发效率和运行效率的问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在php中消息的通知是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般多考虑异步的方式。多级触发时的效率更是让人担忧,设计时注意。

 

 

 

观察者模式的使用场景

1、关联行为场景。需要注意的是,关系行为是可拆分的,而不是“组合”关系

2、事件多级触发场景

3、跨系统的消息交换场景,如消息队列的处理机制