天天看点

设计模式 —— 观察者模式(Observer)设计模式——观察者模式(Observer)

设计模式——观察者模式(Observer)

简介

观察者模式(Observer)

是一种行为型模式,该模式具有

观察者(Observer)

被观察者(Subject)

两种角色对象,一个

被观察者(Subject)

可以具有多个

观察者(Observer)

,是一种一对多的组合关系。该模式的行为方式是:当

被观察者(Subject)

状态发生变化或执行某一操作时,

观察者(Observer)

会被告知并执行相关的操作。下图是该模式的UML图。

设计模式 —— 观察者模式(Observer)设计模式——观察者模式(Observer)

简单实例

场景描述: 一个网站注册了新用户,给该用户发送手机短信通知和邮件通知。此时

用户(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();

           

观察者模式的优缺点

优点

  • 观察者与被观察者间耦合度较低,方便后期的代码维护
  • 通过观察者模式,被观察者可以根据需求方便的增加或删除观察者

缺点

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 观察者知道被观察者发送通知了,但是观察者不知道所观察的对象具体是如何发生变化的
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

继续阅读