观察者模式

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. 观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一方。









以上是关于观察者模式的主要内容,如果未能解决你的问题,请参考以下文章

未调用 LiveData 观察者

Java设计模式补充:回调模式事件监听器模式观察者模式(转)

如何为片段设置观察者

永远观察实时数据的片段

设计模式观察者模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )

观察者模式