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、总结
观察者模式解除了主体和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
Fatal error: Class ‘PHPUnit_Framework_TestCase’ not found in D:\root\test\Observer\Tests\ObserverTest.php on line 12
报错,这是为何~
安装 PHPUnit 了吗
user.php中
public function attach(SplObserver $observer)
{
$this->observers->attach($observer);
}
不是应该是$this->observers[ ] = $observer;吗,那样写怎么感觉是死循环啊
懂了,没看到构造函数有$this->observers = new SplObjectStorage();这句
为什么有了构造函数$this->observers = new SplObjectStorage() 就可以$this->observers->attach($observer);这样写呢?这不还是调用了自己吗?还有,不知道这种设计模式怎么使用到具体场景。做测试,我知道原理,也知道怎么测试。但那是在一个文件里面测试。正常的话,应该是user类发出一个通知,观察者能够正常接收才对。可是现在的测试是,自己设置的user类做出动作,然后自己的内容做出了改变。我觉得这样没有意义了啊
我想$this->observers->attach($observer)这样写是指调用了SplObjectStorage对象的attach方法。添加对象到Storage中
$this->observers = new \SplObjectStorage();
observers 是Spl对象 要看仔细点