登录系统想必大家都做过,验证用户名密码就登录成功,日志系统应该记录此次登录,如果登录出错,安全系统应该会记录此次错误,邮件系统也应该会发送相关邮件给管理员,等等。这就好像登录系统被很多人监视一样,一旦有什么风吹草动,立即会被其它系统获悉。那就用观察者模式来试试,类图如下:
很简单的模式,实现代码:
<?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类负责保证它们的主体是正确的类型。它们甚至能将自己添加到主体上。类图如下:
实现代码如下:
<?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、跨系统的消息交换场景,如消息队列的处理机制