观察者模式
Posted wujuntian
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了观察者模式相关的知识,希望对你有一定的参考价值。
问题:
观察者模式的核心是把客户元素(观察者)从一个中心类(主体)中分离开来。当主体知道事件发生时,观察者需要被通知到。同时,我们并不希望将主体与观察者之间的关系进行硬编码。这样,观察者的代码可被重复使用,不同主体可以随意组合使用多个观察者。
概念:
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有观察者对象,使它们能够自动更新自己。
实现一:
//主体类接口
interface Observable
{
public function getStatus();
public function attach(Observer $observer);
public function detach(Observer $observer);
public function notify();
}
//主体类实现
class Login implements Observable
{
const LOGIN_USER_UNKNOWN = 1;
const LOGIN_WRONG_PASS = 2;
const LOGIN_ACCESS = 3;
private $status;
private $observers;
public function __construct()
{
$this->status = [];
$this->observers = [];
}
private function setStatus($status, $user, $ip)
{
$this->status = [$status, $user, $ip];
}
public function getStatus()
{
return $this->status;
}
public function handleLogin($user, $pass, $ip)
{
switch(rand(1, 3)) {
case 1:
$this->setStatus(self::LOGIN_USER_UNKNOWN, $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 = [];
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
{
public function update(Observable $observable);
}
//观察者实现
class SecurityMonitor implements Observer
{
public function update(Observable $observable)
{
$status = $observable->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Sending mail to sysadmin<br>‘;
}
}
}
class GeneralLogger implements Observer
{
public function update(Observable $observable)
{
$status = $observable->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Adding login data to log<br>‘;
}
}
}
class PartnershipTool implements Observer
{
public function update(Observable $observable)
{
$status = $observable->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Setting cookie if IP matches a list<br>‘;
}
}
}
//应用
$login = new Login();
$login->attach(new SecurityMonitor());
$login->attach(new GeneralLogger());
$login->attach(new PartnershipTool());
$login->handleLogin(‘ajun‘, ‘shiajunhaha‘, ‘127.0.0.1‘);
注:这里需要在SecurityMonitor、GeneralLogger和PartnershipTool类中调用Login::getStatus(),但是无法保证update()接收到的Observable对象一定是一个Login对象,所以为了保证所有的Observable对象都有getStatus()方法,这里采用扩展Observable接口,并在其中添加了getStatus()方法,这样是可以解决问题,但是降低了Observable接口的通用性。
实现二:
保持Observable接口的通用性,由Observer类负责保证它们的主体是正确的类型。
1. 代码实现:
//主体类接口
interface Observable
{
public function attach(Observer $observer);
public function detach(Observer $observer);
public function notify();
}
//主体类实现
class Login implements Observable
{
const LOGIN_USER_UNKNOWN = 1;
const LOGIN_WRONG_PASS = 2;
const LOGIN_ACCESS = 3;
private $status;
private $observers;
public function __construct()
{
$this->status = [];
$this->observers = [];
}
private function setStatus($status, $user, $ip)
{
$this->status = [$status, $user, $ip];
}
public function getStatus()
{
return $this->status;
}
public function handleLogin($user, $pass, $ip)
{
switch(rand(1, 3)) {
case 1:
$this->setStatus(self::LOGIN_USER_UNKNOWN, $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 = [];
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
{
public 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($observable === $this->login) {
$this->doUpdate($observable);
}
}
abstract public function doUpdate(Login $login);
}
//观察者实现
class SecurityMonitor extends LoginObserver
{
public function doUpdate(Login $login)
{
$status = $login->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Sending mail to sysadmin<br>‘;
}
}
}
class GeneralLogger extends LoginObserver
{
public function doUpdate(Login $login)
{
$status = $login->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Adding login data to log<br>‘;
}
}
}
class PartnershipTool extends LoginObserver
{
public function doUpdate(Login $login)
{
$status = $login->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Setting cookie if IP matches a list<br>‘;
}
}
}
//应用
$login = new Login();
new SecurityMonitor($login);
new GeneralLogger($login);
new PartnershipTool($login);
$login->handleLogin(‘ajun‘, ‘shiajunhaha‘, ‘127.0.0.1‘);
2. 类图:
实现三:
通过php内置的SPL扩展来实现观察者模式。主要涉及3个元素:SplObserver、SplSubject和SplObjectStorage,其中SplObserver和SplSubject是分别与“实现一”、“实现二”中的Observer和Observable完全相同的接口,而SplObjectStorage则是一个工具类,用于更好地存储、删除对象。
代码示例:
//主体类实现
class Login implements SplSubject
{
const LOGIN_USER_UNKNOWN = 1;
const LOGIN_WRONG_PASS = 2;
const LOGIN_ACCESS = 3;
private $status;
private $storage;
public function __construct()
{
$this->status = [];
$this->storage = new SplObjectStorage();
}
private function setStatus($status, $user, $ip)
{
$this->status = [$status, $user, $ip];
}
public function getStatus()
{
return $this->status;
}
public function handleLogin($user, $pass, $ip)
{
switch(rand(1, 3)) {
case 1:
$this->setStatus(self::LOGIN_USER_UNKNOWN, $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->attach($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($subject === $this->login) {
$this->doUpdate($subject);
}
}
abstract public function doUpdate(Login $login);
}
//观察者实现
class SecurityMonitor extends LoginObserver
{
public function doUpdate(Login $login)
{
$status = $login->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Sending mail to sysadmin<br>‘;
}
}
}
class GeneralLogger extends LoginObserver
{
public function doUpdate(Login $login)
{
$status = $login->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Adding login data to log<br>‘;
}
}
}
class PartnershipTool extends LoginObserver
{
public function doUpdate(Login $login)
{
$status = $login->getStatus();
if($status[0] == Login::LOGIN_WRONG_PASS) {
echo ‘Setting cookie if IP matches a list<br>‘;
}
}
}
//应用
$login = new Login();
new SecurityMonitor($login);
new GeneralLogger($login);
new PartnershipTool($login);
$login->handleLogin(‘ajun‘, ‘shiajunhaha‘, ‘127.0.0.1‘);
效果:
1. 当一个对象的改变需要同时改变其他对象,而且不知道具体有多少对象需要被改变的时候,就应该考虑使用观察者模式。
2. 观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一方。
以上是关于观察者模式的主要内容,如果未能解决你的问题,请参考以下文章
Java设计模式补充:回调模式事件监听器模式观察者模式(转)