abei1982 2017-05-18 00:02:53 2862次浏览 6条回复 6 3 0

Yii2的事件采用了“观察者模式”,先了解观察者,再学事件就容易了。

它是什么

首先不要被名字所吓倒,往下看,它真的很简单。

先来一个比较高大上的定义:观察者模式定义了一系列对象之间的一对多关系,当一个对象改变状态后,其他依赖者都会收到通知。

看明白了么?

如果没明白我们再来一个生活一点的:观察者模式就是订报纸的模式,你和一些人向某个报社订报纸,只要有新报纸出版,报社就会给你们送来,具体你们怎么看和报社无关,只要报社不倒闭,就会一直给你们送。

当然,你也可以退订。

观察者模式 == 报社 + 订报纸的人

无论你明白了哪种定义,记住一点,“观察者模式”最牛逼的地方就是让一些彼此依赖的类松耦合,牛逼的系统是什么样的?那就是每个对象间的依赖程度降到冰点,但是依然可以交互。

一个活生生的例子

没例子的技术文都是流氓文,我给大家举个例子,让你更形象化的理解观察者模式。

所有的观察者模式中一定有一个主题和一堆观察者,比如北哥兄弟连和每一个订阅会员

对于主题(北哥兄弟连)

每一天的某一刻兄弟连有了新视频,然后我会告诉所有的订阅者。
我仅仅是通知了所有订阅者有新视频了。

对于观察者(订阅者)

订阅者小明:收到后忽略通知
订阅者隔壁老王:立刻开始看视频
订阅者川普:收藏了一下,然后去wc看
订阅者冰冰:立刻开始看视频
等等等等

日复一日,年复一年,主题并不清楚订阅者收到通知后都干嘛了啥,它也不关心,主题的责任就是告诉他们。

对于观察者,就像一个清单,清单中的每个人都会收到新课通知,然后自己进行自己的处理。

突然有一天

订阅者隔壁老王说:“老子不学了,不要再告诉我”。

于是,老王从清单被清除,它不再是观察者,主题也不会给它再发。

Code it

我想到此刻,你已经清楚了观察者模式,但是作为一个语言类文章,不写点代码总说不过去,我们接下来用代码来实现上面的需求。

我们先来一个不用观察者模式的代码

class Video {
	public function new(){
		$checkNewVideo = Video::find()->where("xxxxx")->one();
		if($checkNewVideo){
			//	通知各位
			LaoWang::newVideo();
			XiaoLi::newVideo();
			ChuanPu::newVideo();
			.......
			//	还有很多很多,比如在给某个集体客户群发、短信发等等等等
		}
	}
}

//	具体实现
$model = new Video();
$model->new();

其实,在系统小的时候,这是非常快捷有效的方式。

但是,当系统变大的时候,这种方法马上面临难以扩展的问题,并且容易出错。

  • 比如老王不想订阅了,我们需要改源代码。
  • 比如又增加了一个客户,我们要去改源代码。
  • 比如xxx,我们都需要改源代码。

这两个对象的耦合度太高了。

解决它 - 用观察者模式

我们先改进上面的代码

/**
 * 被观察者接口
 * 定义了一些公用方法声明,使用观察者模式的类都可以继承此接口
 */
interface Observable {
    // 添加/注册观察者
    public function attach(Observer $observer);
    // 删除观察者
    public function detach(Observer $observer);
    // 触发通知
    public function notify();
}

class Video implements Observable {
	public $observers = [];//	订阅者
	
	//	添加观察者
	public function attach(Observer $observer){
		$key = array_search($observer, $this->observers);
		if ($key === false) {
			$this->observers[] = $observer;
		}
	}
	
	//	删除观察者
	public function detach(Observer $observer){
		$key = array_search($observer, $this->observers);
		if ($key !== false) {
			unset($this->observers[$key]);
		}
	}
	
	//	通知所有观察者
	public function notify(){
		foreach ($this->observers as $observer) {
			// 把本类对象传给观察者
			$observer->update($this);
		}
	}
	
	public function  new(){
		$checkNewVideo = Video::find()->where("xxxxx")->one();
		if($checkNewVideo){
			$this->notify();
		}
	}
}

你看到了,修改后的代码并不关心具体发送给谁,它只是遍历了所有观察者的列表,然后告诉他们一下而已,观察者增减对此类不会有任何影响。

对于观察者,数字不定,随时有增减,因此我们定义了一个观察者接口开始抽象它们。

/**
 * 观察者接口
 */
interface Observer
{
    // 接收到通知的处理方法
    public function update(Observable $observable);
}

老王、小明、川普、冰冰订阅了视频

class LaoWang implements Observer {
	public function update(Observable $observable){
		echo "立刻开始看视频";
	}
}

class XiaoMing implements Observer {
	public function update(Observable $observable){
		echo "收到后忽略通知";
	}
}

class ChuanPu implements Observer {
	public function update(Observable $observable){
		echo "收藏了一下,然后去wc看";
	}
}

class BingBing implements Observer {
	public function update(Observable $observable){
		echo "立刻开始看视频";
	}
}

具体实现

$model = new Video();
$model->attach(new LaoWang());
$model->attach(new XiaoLi());
$model->attach(new ChuanPu());
$model->attach(new BingBing());
$model->new();

这样当我们再增加一个人加入 习大大 的时候,我们只需要增加一个习大大的观察者类,在实现的时候添加注册,而不需要去改Video类和其他的观察者类,将类之间的耦合降低了很多。

回头看

上面就是观察者模式,我们先预想一下我们的事件,假设我们定义了很多观察者代码,他们监听事件的发生,当一个事件被触发,这些观察者都会知道,执行各自的逻辑。

事件就是观察者模式的一种应用。

监听系统的某一个行为,实时获取并执行自己负责的代码。

如果你看懂了上面的,恭喜你事件已经入门一半,下一篇开始说Yii的事件,请大家对照着观察者模式,自己体会。

yii技术小站 http://nai8.me

觉得很赞
亿速云
  • 回复于 2017-07-26 15:46 举报

    觉得很赞
  • 回复于 2017-09-10 16:54 举报

    写得不错!!!

  • 回复于 2017-11-13 13:38 举报

    good!

  • 回复于 2017-12-26 17:51 举报
  • 回复于 2018-07-28 16:17 举报

    观察者模式,与不用观察者模式相比,除了多写了一堆的代码,其他并没体现出什么好处。

    具体以你讲的例子来说:

    但是,当系统变大的时候,这种方法马上面临难以扩展的问题,并且容易出错。
    比如老王不想订阅了,我们需要改源代码。
    比如又增加了一个客户,我们要去改源代码。
    比如xxx,我们都需要改源代码。
    这两个对象的耦合度太高了。

    $model = new Video();
    $model->attach(new LaoWang());
    $model->attach(new XiaoLi());
    $model->attach(new ChuanPu());
    $model->attach(new BingBing());
    $model->new();

    $model = new Video();
    $model->new();
    LaoWang::update($model);
    XiaoLi::update($model);
    ChuanPu::update($model);
    BingBing::update($model);

    两个代码相比,

    前者观察者模式,并没有解决上面作者自己所提出来的那些问题:有需求变动,还是要改代码的,如果仔细看看,反而是观察者模式情况下,改动的代码还要更多

    具体来说(还是借用作者的例子):

    “当我们再增加一个人加入 习大大 的时候,我们只需要增加一个习大大的观察者类,在实现的时候添加注册,而不需要去改Video类和其他的观察者类,将类之间的耦合降低了很多”

    这里说了,增加习大大: 一要增加类,二要注册,其实还有个三,就是得触发。好处呢,是:不用改其他观察者类。

    那么,不用观察者模式,如何?其实一样嘛,一要增加类,二是要调用。没了。是不是更简单?
    上面说的好处:不用改其他类,普通的调用一样不用改其他类嘛,所以这个好处也不是优势,不存在的

    不知道作者如何解释。

    3 条回复
    回复于 2018-10-08 16:00 回复

    这样做更好。做到了“开闭原则“

    回复于 2018-10-09 15:55 回复

    开闭原则 无非是找个好借口,仅此而已。试问,开闭原则,能回答上面的问题吗,解决了上述问题了吗?不解决问题,只是耍花枪。很多人看到个新东西,就喜欢人云亦云,不加思考。

    我们讨论观察者模式,还没等说清楚,再来个开闭原则。说的是概念A的事情,没说明白,然后用概念B来说,好嘛,环环相绕无休止,这叫做避而不答。

    回复于 2018-10-17 17:27 回复

    层主质疑得很好,好处应该有,但是真心没有说出来。
    我觉得好处应该是:避免污染主逻辑!
    未使用观察者模式,其实是面向过程式编程,某个逻辑改动会影响到整个逻辑的删减
    使用观察者模式,其实是面向对象了,具体的观察者和被观察者的交互已经写好了(foreach遍历),重点只是注意一下观察者的删减即可,不会影响到主逻辑

    , 觉得很赞
  • 回复于 2018-12-26 11:20 举报
    $model = new Video();
    $model->new();
    LaoWang::update($model);
    XiaoLi::update($model);
    ChuanPu::update($model);
    BingBing::update($model);
    

    层主的反例相当与把subject里面的notify方法拿出来拆分过程,实际上还是属于观察者模式的范畴

    1. 被观察者在接到一个消息之后($model->new())通知所有的观察者,被把自己的上下文传递给Observer
    2. 过程式的方式应该在 $model->new() 里面去集成逻辑,上面的方式已经实现了解耦;可以做到只新增不修改的目的
    1 条回复
    回复于 2018-12-26 11:30 回复

    补充: 这样的好处还有一项, 方便测试,方便测试,方便测试;

    觉得很赞
您需要登录后才可以回复。登录 | 立即注册