观察者模式
观察者模式,也称发布-订阅模式,定义了一个被观察者和多个观察者的、一对多的对象关系。
在被观察者状态发生变化的时候,它的所有观察者都会收到通知,并自动更新。
观察者模式通常用在实时事件处理系统、组件间解耦、数据库驱动的消息队列系统,同时也是MVC设计模式中的重要组成部分。
以下我们以订单创建为例。
当订单创建后,系统会发送邮件和短信,并保存日志记录。
1 问题
在没有用观察者模式的时候,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Order { private $state = 0;
public function addOrder() { $this->state = 1; Email::update($this->state); Message::update($this->state); Log::update(); } }
|
代码中,在Order类中调用各类的方法来实现通知。当在客户端创建订单:
1 2 3 4
| $order = new Order(); $order->addOrder();
|
就会同时产生三个通知:发送邮件、发送短信和记录日志。
在系统小的时候,这是非常快捷有效的方式。
可是,当系统变大的时候,这种方法马上面临难以扩展的问题,并且容易出错:
如果订单不需要某种通知,比如不需要记录日志,则必须修改Order类,做状态的判断;
如果再加一种通知方式,如系统消息通知,则除了增加新类,同时还需要修改Order类和客户端。
这两条都不符合面向对象中的开闭原则,会让系统越来越难维护。
2 解决
接下来我们用观察者模式解决这个问题。
2.1 被观察者
被观察者是一些具体的实例,比如订单管理、用户登陆、评论回复、状态审核等等。
别的功能会依赖于它们的状态进行各种动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
interface Observable { public function attach(Observer $observer); public function detach(Observer $observer); public function 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]); } }
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()方法,这是接收被观察者通知的唯一渠道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
interface Observer { public function update(Observable $observable); }
class Email implements Observer { public function update(Observable $observable) { $state = $observable->getState(); if ($state) { echo '发送邮件:您已经成功下单。'; } else { echo '发送邮件:下单失败,请重试。'; } } }
class Message implements Observer { public function update(Observable $observable) { $state = $observable->getState(); if ($state) { echo '短信通知:您已下单成功。'; } else { echo '短信通知:下单失败,请重试。'; } } }
class Log implements Observer { public function update(Observable $observable) { echo '记录日志:生成了一个订单记录。'; } }
|
这里有三个观察者:发送邮件、短信通知和记录日志,它们都实现了update()方法。
其中,发送邮件和短信依赖于订单、也就是被观察者的状态,来决定发送消息的内容,记录日志则不需要订单的状态。
2.3 客户端
然后我们创建一个客户端,内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
$email = new Email(); $message = new Message(); $log = new Log();
$order = new Order();
$order->attach($email); $order->attach($message); $order->attach($log);
$order->addOrder();
echo '<br />';
$order->detach($log);
$order->addOrder();
|
执行应用后,会输出这样的消息:
发送邮件:您已经成功下单。添加日志:生成了一个订单记录。系统消息:您已下单成功。
发送邮件:您已经成功下单。添加日志:生成了一个订单记录。
对于不需要通知的观察者,用detach()移出观察者列表即可。
这种情况就解开了类之间的耦合。
2.4 新增观察者
如果再需要新增一个观察者,如下,只需要添加观察者类本身,实现update()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
class Alert implements Observer { public function update(Observable $observable) { echo '系统消息:您的订单有更新了~~~'; } } 再到客户端中注册Alert类到观察者列表中:
$alert = new Alert();
$order->attach($alert);
|
就能订阅被观察者的通知。
3 特点
在观察者模式中,被观察者完全不需要关心观察者,在自身状态有变化是,遍历执行观察者update()方法即完成通知。
在观察者模式中,被观察者通过添加attach()方法,提供给观察者注册,使自己变得可见。
当被观察者改变时,给注册的观察者发送通知。至于观察者如何处理通知,被观察者不需要关心。
这是一种良好的设计,对象之间不必相互理解,同样能够相互通信。
面向对象编程中,任何对象的状态都非常重要,它们是对象间交互的桥梁。
当一个对象的改变需要被其他对象关注时,观察者模式就派上用场了。