观察者模式
创建依赖关系,当系统事件发生时通知观察者对象。
正交性是我们之前讨论过的一个话题。程序员的目标应该是创建在改动和转移时对其他组件影响最小的组件。当然,正交性通常只是个梦想,然而你可以使用不同的策略来使引用尽量减少。
下面假设有个负责处理用户登录的类:
class Login
{
const LOGIN_USER_UNKNOW = 1;
const LONGIN_WRONG_PASS = 2;
const LOGIN_ACCESS = 3;
private $status = array();
function handleLogin($user, $pass, $ip)
{
switch (rand(1, 3)) {
case 1:
$this->setStatus(self::LOGIN_ACCESS, $user, $ip);
$ret = true;
break;
case 2:
$this->setStatus(self::LONGIN_WRONG_PASS, $user, $ip);
$ret = false;
break;
case 3:
$this->setStatus(self::LOGIN_USER_UNKNOW, $user, $ip);
$ret = false;
break;
}
Logger::logIP();
return $ret;
}
private function setStatus($status, $user, $ip)
{
$this->status = array($status, $user, $ip);
}
function getStatus()
{
return $this->status;
}
}
考虑到到安全问题,系统管理员可能会要求有用户登录失败时发一封邮件到管理员的邮箱。你可以再一次回到登录方法并添加一个新的调用:
if (!$ret) {
Notifier::mailWarning($user, $ip, $this->getStatus());
}
随着新功能的不断加入,这个类会紧紧嵌入到系统中。那么我们该如何拯救呢?观察者模式用在这里最合适不过了。
实现:
观察者模式的核心是把客户元素(观察者)从一个中心类(主体)中分离开来。当主体知道事件发生时,观察者需要被通知到。同时,我们并不希望主体与观察者之间的关系进行硬编码。
interface Observable
{
function attach(Observer $observer);
function detach(Observer $observer);
function notify();
}
class Login implements Observable
{
private $observers;
const LOGIN_USER_UNKNOW = 1;
const LONGIN_WRONG_PASS = 2;
const LOGIN_ACCESS = 3;
private $status = array();
function __construct()
{
$this->observers = array();
}
function handleLogin($user, $pass, $ip)
{
switch (rand(1, 3)) {
case 1:
$this->setStatus(self::LOGIN_ACCESS, $user, $ip);
$ret = true;
break;
case 2:
$this->setStatus(self::LONGIN_WRONG_PASS, $user, $ip);
$ret = false;
break;
case 3:
$this->setStatus(self::LOGIN_USER_UNKNOW, $user, $ip);
$ret = false;
break;
}
$this->notify();
return $ret;
}
function attach(Observer $observer)
{
$this->observers[] = $observer;
}
function detach(Observer $observer)
{
$newobservers = array();
foreach ($this->observers as $obs) {
if (($obs !== $observer)) {
$newobservers[] = $obs;
}
}
$this->observers = $newobservers;
}
function notify()
{
foreach ($this->observers as $obs) {
$obs->update($this);
}
}
private function setStatus($status, $user, $ip)
{
$this->status = array($status, $user, $ip);
}
function getStatus()
{
return $this->status;
}
}
然后让我们定义Observer接口:
interface Observer
{
function update(Observable $observable);
}
class SecurityMonitor extends Observer
{
function update(Observable $observable)
{
$status = $observable->getStatus();
if ($status[0] == Login::LONGIN_WRONG_PASS) {
//发送邮件
print __CLASS__ . ":\tsending mail to sysadmin\n";
}
}
}
$login = new Login();
$login->attach(new SecurityMonitor());
虽然调用发生在一个Observable对象上,但无法保证该对象也是一个Login对象。有一个办法:
abstract class LoginObserver implements Observer
{
private $login;
function __construct(Login $login)
{
$this->login = $login;
$login->attach($this);
}
function update(Observable $observable)
{
if ($observable === $this->login) {
$this->doUpdate($observable);
}
}
abstract function doUpdate(Login $login);
}
class SecurityMonitor extends LoginObserver
{
function doUpdate(Login $login)
{
$status = $login->getStatus();
if ($status[0] == Login::LONGIN_WRONG_PASS) {
//发送邮件
print __CLASS__ . ":\tsending mail to sysadmin\n";
}
}
}
class GeneralLogger extends LoginObserver
{
function doUpdate(Login $login)
{
$status = $login->getStatus();
if ($status[0] == Login::LONGIN_WRONG_PASS) {
//记录登录数据到日志
print __CLASS__ . ":\tadd login data to log\n";
}
}
}
class PartnershipTool extends LoginObserver
{
function doUpdate(Login $login)
{
$status = $login->getStatus();
if ($status[0] == Login::LONGIN_WRONG_PASS) {
//检查IP地址,如果匹配列表,则设置cookie
print __CLASS__ . ":\tset cookie if IP matches a list\n";
}
}
}
当实例化对象时,创建和添加LoginObserver类的任务就完成了:
$login = new Login();
new SecurityMonitor($login);
new GeneralLogger($login);
new PartnershipTool($login);
PHP通过内置的SPL扩展提供了对观察者模式的原生支持。SPL是一套可以帮助程序员处理很多面向对象问题的工具。下面是一个改进过的示例代码:
class Login implements SplSubject
{
private $storage;
//...
function __construct()
{
$this->storage = new SplObjectStorage();
}
function attach(SplObserver $observer)
{
$this->storage->attach($observer);
}
public function detach(SplObserver $observer)
{
$this->storage->detach($observer);
}
function notify()
{
foreach ($this->storage as $obs) {
$obs->update($this);
}
}
//...
}
abstract class LoginObserver implements SplObserver
{
private $login;
function __construct(Login $login)
{
$this->login = $login;
$login->attach($this);
}
function update(SplSubject $subject)
{
if ($subject === $this->login) {
$this->doUpdate($subject);
}
}
abstract function doUpdate(Login $login);
}