观察者模式
观察者模式,也称发布-订阅模式,定义了一个被观察者和多个观察者的、一对多的对象关系。
在被观察者状态发生变化的时候,它的所有观察者都会收到通知,并自动更新。
观察者模式通常用在实时事件处理系统、组件间解耦、数据库驱动的消息队列系统,同时也是MVC设计模式中的重要组成部分。
以下我们以订单创建为例。
当订单创建后,系统会发送邮件和短信,并保存日志记录。
1 问题
在没有用观察者模式的时候,如下:
class Order { // 订单状态 private $state = 0; // 订单状态有变化时发送通知 public function addOrder() { $this->state = 1; // 发送邮件 Email::update($this->state); // 短信通知 Message::update($this->state); // 记录日志 Log::update(); // 其他更多通知 } }
代码中,在Order
类中调用各类的方法来实现通知。当在客户端创建订单:
$order = new Order(); $order->addOrder();
就会同时产生三个通知:发送邮件、发送短信和记录日志。
在系统小的时候,这是非常快捷有效的方式。
可是,当系统变大的时候,这种方法马上面临难以扩展的问题,并且容易出错:
- 如果订单不需要某种通知,比如不需要记录日志,则必须修改
Order
类,做状态的判断; - 如果再加一种通知方式,如系统消息通知,则除了增加新类,同时还需要修改
Order
类和客户端。
这两条都不符合面向对象中的开闭原则,会让系统越来越难维护。
2 解决
接下来我们用观察者模式解决这个问题。
2.1 被观察者
被观察者是一些具体的实例,比如订单管理、用户登陆、评论回复、状态审核等等。
别的功能会依赖于它们的状态进行各种动作。
/** * 被观察者接口 */ interface Observable { // 添加/注册观察者 public function attach(Observer $observer); // 删除观察者 public function detach(Observer $observer); // 触发通知 public function notify(); } /** * 被观察者 * 职责:添加观察者到$observers属性中, * 有变动时通过notify()方法运行通知 */ class Order implements Observable { // 保存观察者 private $observers = array(); // 订单状态 private $state = 0; // 添加(注册)观察者 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]); } } // 遍历调用观察者的update()方法进行通知,不关心其具体实现方式 public function notify() { foreach ($this->observers as $observer) { // 把本类对象传给观察者,以便观察者获取当前类对象的信息 $observer->update($this); } } // 订单状态有变化时发送通知 public function addOrder() { $this->state = 1; $this->notify(); } // 获取提供给观察者的状态 public function getState() { return $this->state; } }
被观察者至少要实现attach()
、detach()
、notify()
三个方法,用以添加、删除和通知观察者。
通知的方式是,在类中的其他方法(如:创建订单)调用notify()
方法。
另外,观察者可能用到被观察者的一些状态信息。
所以,要在notify()
中把当前对象作为参数传给观察者,方便其通过提供的public
方法获得被观察者的状态信息。
本例用getState()
方法供给观察者获取状态信息。
2.2 观察者
观察者可能有多个,但每个观察者都必须实现Observer
接口规定的update()
方法,这是接收被观察者通知的唯一渠道。
/** * 观察者接口 */ interface Observer { // 接收到通知的处理方法 public function update(Observable $observable); } /** * 观察者1:发送邮件 */ class Email implements Observer { public function update(Observable $observable) { $state = $observable->getState(); if ($state) { echo '发送邮件:您已经成功下单。'; } else { echo '发送邮件:下单失败,请重试。'; } } } /** * 观察者2:短信通知 */ class Message implements Observer { public function update(Observable $observable) { $state = $observable->getState(); if ($state) { echo '短信通知:您已下单成功。'; } else { echo '短信通知:下单失败,请重试。'; } } } /** * 观察者3:记录日志 */ class Log implements Observer { public function update(Observable $observable) { echo '记录日志:生成了一个订单记录。'; } }
这里有三个观察者:发送邮件、短信通知和记录日志,它们都实现了update()
方法。
其中,发送邮件和短信依赖于订单、也就是被观察者的状态,来决定发送消息的内容,记录日志则不需要订单的状态。
2.3 客户端
然后我们创建一个客户端,内容:
// 创建观察者对象 $email = new Email(); $message = new Message(); $log = new Log(); // 创建订单对象 $order = new Order(); // 向订单对象中注册3个观察者:发送邮件、短信通知、记录日志 $order->attach($email); $order->attach($message); $order->attach($log); // 添加订单,添加时会自动发送通知给观察者 $order->addOrder(); echo '<br />'; // 删除记录日志观察者 $order->detach($log); // 添加另一个订单,会再次发送通知给观察着 $order->addOrder();
执行应用后,会输出这样的消息:
发送邮件:您已经成功下单。添加日志:生成了一个订单记录。系统消息:您已下单成功。 发送邮件:您已经成功下单。添加日志:生成了一个订单记录。
对于不需要通知的观察者,用detach()
移出观察者列表即可。
这种情况就解开了类之间的耦合。
2.4 新增观察者
如果再需要新增一个观察者,如下,只需要添加观察者类本身,实现update()
方法。
/** * 观察者4:系统消息 */ class Alert implements Observer { public function update(Observable $observable) { echo '系统消息:您的订单有更新了~~~'; } }
再到客户端中注册Alert
类到观察者列表中:
// 创建“系统消息”观察者 $alert = new Alert(); // 注册观察者到订单对象中 $order->attach($alert);
就能订阅被观察者的通知。
3 特点
在观察者模式中,被观察者完全不需要关心观察者,在自身状态有变化是,遍历执行观察者update()
方法即完成通知。
在观察者模式中,被观察者通过添加attach()
方法,提供给观察者注册,使自己变得可见。
当被观察者改变时,给注册的观察者发送通知。至于观察者如何处理通知,被观察者不需要关心。
这是一种良好的设计,对象之间不必相互理解,同样能够相互通信。
UML如下:
面向对象编程中,任何对象的状态都非常重要,它们是对象间交互的桥梁。
当一个对象的改变需要被其他对象关注时,观察者模式就派上用场了。
参考资料:
第一个问题是为啥需要observer呢,我用过的magento就使用了observer但是不太明白原因。想js里面就有event listener那个是因为要监听某个事件,php里面也需要类似监听的动作吗?
比如产生一个order就需要激活三个动作:发信、发短信、做记录。那不用observer不是也需要这三个动作么,所以第二个问题就是你的例子里面就是在产生order的时候给自己attach了一个observer。然后等observer把活干完了再detach了,这个和产生order后直接调用一个专门发信发短信和做记录的class有啥区别呢?
观察者就是接收通知的对象,和我们订阅微信公众号一个道理。
公众号有更新时,我们直接就能收到最新点消息。
这个过程关键的一点时,我们并不需要去修改公众号的任何功能,只需要把自己加到它的通知列表里面就行了。
在观察者里面,就是,不需要修改原来的类,就能够实现订阅功能,符合开闭原则。
第二,假设我们做一个cms系统,因为是一个通用的系统,所以并不知用户(指开发者)使用的时候,要通过哪些方式发消息,怎么办?
好的方式是,提供一个方法,让用户调用这个方法,把自己的通知方式
attach
进去,其他的就不用管了,到后面被观察者自动执行了。另一方面,假设我们的cms默认开启了发短信通知,但是用户不需要这个。
这样让他直接去改核心代码也是不好的实践,直接调用
detach
方法就好了。正在学习中…希望快些更新哈
好的,感谢支持
讲的非常清楚,看了一遍就看懂了,希望快点更新哈,学习了
好的
简单,清晰,明了。get~ 😀
接口里面没有定义getState,哪些update的实现里面能调用么?
getState()方法提供给提供一个途径,让观察者可以获得被观察者的状态,但是并不是所有观察者都需要这个状态。
很多的观察者可以直接忽略这个状态,比如说,观察者可以直接发消息:您的订单已经收到。
其实不需要告诉用户成功还是失败,如果失败了,应该在程序内部就处理好。
上面那个UML图 Order实现Observerable 应该是一个虚线空心箭头吧
嗯,实现是虚线的:)
讲的很清楚,赞一个
为什么需要定义成接口,不太理解?
因为需要规范,这个接口可能还有很多实现。