观察者模式(SplObserver/SplSubject)
观察者设计模式基本上允许一个对象(主体)维护一个观察者列表,当该对象的状态发生变化时,观察者会自动收到通知。
这种模式在对象之间应用了一对多的依赖关系;总是一个主体更新多个观察者。
四人帮最初认为,这种模式尤其适用于一个抽象有两个方面,其中一个方面依赖于另一个方面的情况。除此之外,当改变一个对象需要改变其他对象,而你又不知道有多少其他对象需要改变时,这种模式也非常有用。最后,当一个对象需要通知其他对象而无需假设这些对象是什么时,这种模式也非常有用,因此这种模式非常适合松散耦合这种关系。
PHP 提供了一个非常有用的接口,称为 SplObserver
和 SplSubject
。这些接口提供了实现观察者设计模式的模板,但实际上并不实现任何功能。
从本质上讲,当我们实现这种模式时,我们允许无限量的对象来观察主体中的事件。通过调用主体对象中的 attach
方法,我们可以将观察者附加到主体上。当主体发生变化时,主体的 notify
方法可以遍历观察者,并多态地调用它们的 update
方法。
我们还可以在主体中调用 un-notify 方法,从而阻止观察者对象观察主体对象。
有鉴于此,主体类包含了从自身附加和分离观察者的方法,该类还包含了更新正在观察它的观察者的 notify
方法。因此,PHP 的 SplSubject
接口如下:
interface SplSubject {
public function attach (SplObserver $observer);
public function detach (SplObserver $observer);
public function notify ();
}
与之相比,我们的 SplObserver
接口看起来更加简单;它只需要实现一个方法,就能让主体更新观察者:
interface SplObserver {
public function update (SplSubject $subject);
}
现在,让我们看看如何实现这两个接口来实现这种设计模式。在这个示例中,我们将有一个新闻源类,它将更新正在阅读该类的各种读者。
让我们定义 Feed
类,它将实现 SplSubject
接口:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-02.adoc - include::example$/Chapter 5/Observer/Feed.php[]
总的来说,我们所介绍的实现方法非常简单。请注意它是如何使用我们之前在本书中探讨过的 spl_object_hash
函数来轻松分离对象的。通过使用哈希值作为数组的键,我们可以快速找到给定的对象,而无需进行操作。
现在我们可以定义 Reader
类,它将实现 SplObserver
接口:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-02.adoc - include::example$Chapter 5/Observer/Reader.php[]
让我们将所有这些内容一起包装在我们的 index.php 文件中,如下所示:
Unresolved include directive in modules/ROOT/pages/ch05/ch5-02.adoc - include::example$/Chapter 5/Observer/index.php[]
在这个脚本中,我们首先实例化一个包含三个阅读器的 feed。我们连接所有阅读器,然后分离其中一个。最后,我们发送一个新的警报,产生以下输出:

这种设计模式的主要优势在于观察者和主体之间的松散耦合关系。由于主体和观察者可以独立变化,因此模块化程度更高。除此之外,我们还可以添加任意数量的观察者,提供任意数量的功能。这种可扩展性和可定制性通常是这种设计模式在应用程序视图中应用的原因,也是它经常在模型-视图-控制器(MVC)框架中实现的原因。
使用这种模式的缺点是,当我们需要调试整个程序时,流程控制会变得很困难,因为观察者之间互不相识。除此之外,还会产生更新开销,在处理特别大的观察者时,会给内存管理带来困难。
请记住,这种设计模式只能在一个程序中使用,它不是为进程间通信或消息系统设计的。在本书的后面部分,我们将介绍当我们希望在不同进程(而不仅仅是一个进程中的不同类)之间进行互通时,如何使用消息模式来描述消息解析系统的不同部分如何相互连接。