1. 模式定义
观察者模式有时也被称作发布/订阅模式,该模式用于为对象实现发布/订阅功能:一旦主体对象状态发生改变,与之关联的观察者对象会收到通知,并进行相应操作。
将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。 消息队列系统、事件都使用了观察者模式。
PHP 为观察者模式定义了两个接口:SplSubject 和 SplObserver。SplSubject 可以看做主体对象的抽象,SplObserver 可以看做观察者对象的抽象,要实现观察者模式,只需让主体对象实现 SplSubject ,观察者对象实现 SplObserver,并实现相应方法即可。
2. UML类图
image.png
3. 示例代码
User.php 实现SplSubject的对象
namespace DesignPattern\Behavioral\Observer;
use SplObserver;
/**
* 观察者模式 : 被观察对象 (主体对象)
*
* 主体对象维护观察者列表并发送通知
*
*/
class User implements \SplSubject
{
/**
* 用户数据
*
* @var array
*/
protected $data = array();
/**
* 观察者对象集
*
* @var \SplObjectStorage
*/
protected $observers;
public function __construct()
{
$this->observers = new \SplObjectStorage();
}
/**
* 附加观察者
* @link https://php.net/manual/en/splsubject.attach.php
* @param SplObserver $observer
* The SplObserver to attach.
*
* @return void
* @since 5.1.0
*/
public function attach(SplObserver $observer)
{
$this->observers->attach($observer);
}
/**
* 取消观察者
* @link https://php.net/manual/en/splsubject.detach.php
* @param SplObserver $observer
* The SplObserver to detach.
*
* @return void
* @since 5.1.0
*/
public function detach(SplObserver $observer)
{
$this->observers->detach($observer);
}
/**
* 通知观察者此用户进行更新
* @link https://php.net/manual/en/splsubject.notify.php
* @return void
* @since 5.1.0
*/
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 实现SplObserver的对象
namespace DesignPattern\Behavioral\Observer;
use SplSubject;
class UserObserver implements \SplObserver
{
/**
* 收到观察对象被更新的消息,进行操作
* @link https://php.net/manual/en/splobserver.update.php
* @param SplSubject $subject
* 这个 SplSubject通知观察者更新
*
* @return void
* @since 5.1.0
*/
public function update(SplSubject $subject)
{
echo get_class($subject) . ' 被更新';
}
}
单元测试
namespace DesignPattern\Tests;
use DesignPattern\Behavioral\Observer\User;
use DesignPattern\Behavioral\Observer\UserObserver;
use PHPUnit\Framework\TestCase;
/**
* 观察者模式测试
* Class ObserverTest
* @package DesignPattern\Tests
*/
class ObserverTest extends TestCase
{
protected $observer;
protected function setUp(): void
{
$this->observer = new UserObserver();
}
/**
* 测试通知
*/
public function testNotify()
{
$this->expectOutputString('DesignPattern\Behavioral\Observer\User 被更新');
$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));
}
}