设计模式——观察者模式(Observer)
简介
观察者模式(Observer)
是一种行为型模式,该模式具有
观察者(Observer)
与
被观察者(Subject)
两种角色对象,一个
被观察者(Subject)
可以具有多个
观察者(Observer)
,是一种一对多的组合关系。该模式的行为方式是:当
被观察者(Subject)
状态发生变化或执行某一操作时,
观察者(Observer)
会被告知并执行相关的操作。下图是该模式的UML图。
简单实例
场景描述: 一个网站注册了新用户,给该用户发送手机短信通知和邮件通知。此时
用户(User)
是一个
被观察者
,
手机通知
和
邮件通知
是
观察者
。为了方便代码的实现,我们可以先定义观察者接口和被观察者接口。
// 接口定义
// 观察者接口
interface Observer
{
// 当被观察者某一状态被触发时,调用该方法进行操作
public function run(Subject $subject);
}
// 被观察者接口
interface Subject
{
// 绑定注册 观察者
public function attachObserver(Observer $observer);
// 删除 观察者
public function detachObserver(Observer $observer);
// 通知 观察者
public function notify();
}
接着我们让用户
User
来实现接口
Subject
,让手机通知
MoblieNotify
和邮件通知
EmailNotify
实现接口
Observer
。
// 定义User类
class User implements Subject
{
// 存储观察者
protected $observers;
// 注册观察者
public function attachObserver(Observer $observer)
{
$this->observers[] = $observer;
}
// 删除观察者
public function detachObserver(Observer $observer)
{
$key = array_search($this->observers, $observer);
if($key !== false){
unset($this->observers[$key]);
}
}
// 通知观察者
public function notify()
{
if(empty($this->observers)) return;
foreach($this->observers as $observer){
$observer->run($this);
}
}
// 用户注册
public function register()
{
// 注册流程...
$this->notify();
}
}
// 定义短信通知类
class MoblieNotify implements Observer
{
public function run(Subject $subject)
{
echo "短信通知";
}
}
// 定义邮件通知类
class EmailNotify implements Observer
{
public function run(Subject $subject)
{
echo "邮件通知";
}
}
定义好相关类后,我们来调用实现它们。
$user = new User();
$user->attachObserver(new MoblieNotify()); # 将短信通知绑定到用户中
$user->attachObserver(new EmailNotify()); # 将邮件通知绑定到用户中
$user->register();
// 执行以上代码,会有以下输出:
// 短信通知邮件通知
使用PHP标准库(SPL)来实现观察者模式
以上案例是一种常规的观察者模式实现。除此之外,PHP标准库(SPL)也提供了
SplSubject
和
SplObserver
两个接口来辅助我们实现观察者模式,接口的具体信息可以去官方文档查看。下面,我们可以简单实现对以上两个接口的应用,场景是:
蓝色方英雄寒冰和酒桶蹲在红色方中塔附近下半部分的草丛里,红色方英雄提莫走过来准备种蘑菇,当提莫靠近寒冰和酒桶后,酒桶果断顶着肚皮撞向提莫,与此同时寒冰也开大射向提莫(结果提莫就...)
。在这个场景中,提莫充当的是
被观察者
,寒冰和酒桶充当的是
观察者
。
// 提莫
class Teemo implements \SplSubject
{
protected $observers;
// 初始化
public function __construct()
{
# SplObjectStorage是SPL提供的一个类,实现对象到数据的映射,通过该类可以方便的实现数据的相关操作
$this->observers = new \SplObjectStorage();
}
// 添加观察者
public function attach(\SplObserver $observer)
{
$this->observers->attach($observer);
}
// 删除观察者
public function detach(\SplObserver $observer)
{
$this->observers->detach($observer);
}
// 通知观察者
public function notify()
{
if(empty($this->observers)) return;
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
// 种蘑菇
public function mushroom()
{
// 去草丛种蘑菇
$this->notify();
}
}
// 寒冰
class Ashe implements \SplObserver
{
public function update(\SplSubject $subject)
{
echo "我要开大射人";
}
}
// 酒桶
class Gragas implements \SplObserver
{
public function update(\SplSubject $subject)
{
echo "我要用肚皮撞人";
}
}
// 场景实现
$teemo = new Teemo();
$teemo->attach(new Ashe());
$teemo->attach(new Gragas());
$teemo->mushroom();
观察者模式的优缺点
优点
- 观察者与被观察者间耦合度较低,方便后期的代码维护
- 通过观察者模式,被观察者可以根据需求方便的增加或删除观察者
缺点
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
- 观察者知道被观察者发送通知了,但是观察者不知道所观察的对象具体是如何发生变化的
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃