观察者模式(Observer)
1、模式定义
观察者模式有时也被称作发布/订阅模式,该模式用于为对象实现发布/订阅功能:一旦主体对象状态发生改变,与之关联的观察者对象会收到通知,并进行相应操作。
将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。
消息队列系统、事件都使用了观察者模式。
PHP 为观察者模式定义了两个接口:SplSubject 和 SplObserver。SplSubject 可以看做主体对象的抽象,SplObserver 可以看做观察者对象的抽象,要实现观察者模式,只需让主体对象实现 SplSubject ,观察者对象实现 SplObserver,并实现相应方法即可。
2、UML类图
3、示例代码
User.php
<?php
namespace DesignPatterns\Behavioral\Observer;
/**
* 观察者模式 : 被观察对象 (主体对象)
*
* 主体对象维护观察者列表并发送通知
*
*/
class User implements \SplSubject
{
/**
* user data
*
* @var array
*/
protected $data = array();
/**
* observers
*
* @var \SplObjectStorage
*/
protected $observers;
public function __construct()
{
$this->observers = new \SplObjectStorage();
}
/**
* 附加观察者
*
* @param \SplObserver $observer
*
* @return void
*/
public function attach(\SplObserver $observer)
{
$this->observers->attach($observer);
}
/**
* 取消观察者
*
* @param \SplObserver $observer
*
* @return void
*/
public function detach(\SplObserver $observer)
{
$this->observers->detach($observer);
}
/**
* 通知观察者方法
*
* @return void
*/
public function notify()
{
/** @var \SplObserver $observer */
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
/**
*
* @param string $name
* @param mixed $value
*
* @return void
*/
public function __set($name, $value)
{
$this->data[$name] = $value;
// 通知观察者用户被改变
$this->notify();
}
}
UserObserver.php
<?php
namespace DesignPatterns\Behavioral\Observer;
/**
* UserObserver 类(观察者对象)
*/
class UserObserver implements \SplObserver
{
/**
* 观察者要实现的唯一方法
* 也是被 Subject 调用的方法
*
* @param \SplSubject $subject
*/
public function update(\SplSubject $subject)
{
echo get_class($subject) . ' has been updated';
}
}
4、测试代码
Tests/ObserverTest.php
<?php
namespace DesignPatterns\Behavioral\Observer\Tests;
use DesignPatterns\Behavioral\Observer\UserObserver;
use DesignPatterns\Behavioral\Observer\User;
/**
* ObserverTest 测试观察者模式
*/
class ObserverTest extends \PHPUnit_Framework_TestCase
{
protected $observer;
protected function setUp()
{
$this->observer = new UserObserver();
}
/**
* 测试通知
*/
public function testNotify()
{
$this->expectOutputString('DesignPatterns\Behavioral\Observer\User has been updated');
$subject = new User();
$subject->attach($this->observer);
$subject->property = 123;
}
/**
* 测试订阅
*/
public function testAttachDetach()
{
$subject = new User();
$reflection = new \ReflectionProperty($subject, 'observers');
$reflection->setAccessible(true);
/** @var \SplObjectStorage $observers */
$observers = $reflection->getValue($subject);
$this->assertInstanceOf('SplObjectStorage', $observers);
$this->assertFalse($observers->contains($this->observer));
$subject->attach($this->observer);
$this->assertTrue($observers->contains($this->observer));
$subject->detach($this->observer);
$this->assertFalse($observers->contains($this->observer));
}
/**
* 测试 update() 调用
*/
public function testUpdateCalling()
{
$subject = new User();
$observer = $this->getMock('SplObserver');
$subject->attach($observer);
$observer->expects($this->once())
->method('update')
->with($subject);
$subject->notify();
}
}
5、总结
观察者模式解除了主体和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
15 Comments
开篇的第一句话("观察者模式有时也被称作发布/订阅模式")是有本质错误的。
观察者模式中Subject和Observer之间是存在耦合的;
但是发布/订阅模式中的Publisher和Subscriber之间是解耦合的,发布者与订阅者通过消息中间件进行解耦合。
这句话来自《大话设计模式》一书,我们可以把 subject 看作发布者,observer 看作订阅者,反过来,可以把发布订阅模式看作松耦合的观察者模式实现,也没什么错,在 GoF 的《设计模式》一书中,对观察者模式的定义只是一个基本概念,没有对这些细节做定义,在 23 种设计模式中没有发布订阅模式,发布/订阅模式其实是从消息系统中作为架构模式迁移而来,在 Windows 系统中也将这两个模式视作同义词,你可以参考维基百科https://en.wikipedia.org/wiki/Observer_pattern#Coupling_and_typical_pub-sub_implementations中的介绍,从宽松的角度来说,把它们看作一个模式没什么问题,发布/订阅模式只是观察者模式的一种松耦合实现,从严格角度来说,也可以把它们看作不同的模式,比如 《JavaScript 设计模式》一书中就做了这样的区分:https://www.oreilly.com/library/view/learning-javascript-design/9781449334840/ch09s05.html,看你的理解角度了,比如微服务和服务化,有些人认为它们就是一个东西,有些人则要严格区分它们。
「本质错误」这样的措辞有点夸张了。
如吧,"本质错误"我收回。 但是"观察者模式有时也被称作发布/订阅模式"中的"也被称作"这个描述实在是太不严谨了,或者这么说也行,但至少在后文,应该也要提一下两者的差异,不然会让人产生一种"两者等同"的认知,会有点误导。