觀察者模式有時也被稱作發(fā)布/訂閱模式灵奖,該模式用于為對象實現(xiàn)發(fā)布/訂閱功能:一旦主體對象狀態(tài)發(fā)生改變卷玉,與之關(guān)聯(lián)的觀察者對象會收到通知,并進行相應操作宁昭。
將一個系統(tǒng)分割成一個一些類相互協(xié)作的類有一個不好的副作用跌宛,那就是需要維護相關(guān)對象間的一致性。我們不希望為了維持一致性而使各類緊密耦合积仗,這樣會給維護疆拘、擴展和重用都帶來不便。觀察者就是解決這類的耦合關(guān)系的寂曹。
消息隊列系統(tǒng)哎迄、事件都使用了觀察者模式。
PHP 為觀察者模式定義了兩個接口:SplSubject
和 SplObserver
隆圆。SplSubject
可以看做主體對象的抽象漱挚,SplObserver
可以看做觀察者對象的抽象,要實現(xiàn)觀察者模式渺氧,只需讓主體對象實現(xiàn) SplSubject 旨涝,觀察者對象實現(xiàn) SplObserver,并實現(xiàn)相應方法即可侣背。
示例1
//下面兩個接口白华,在php已經(jīng)有了,直接implements即可贩耐,
interface IObserver
{
function onChanged( $sender, $args );
}
interface IObservable
{
function addObserver( $observer );
}
class UserList implements IObservable
{
private $_observers = array();
public function addCustomer( $name )
{
foreach( $this->_observers as $obs )
$obs->onChanged( $this, $name );
}
public function addObserver( $observer )
{
$this->_observers []= $observer;
}
}
class UserListLogger implements IObserver
{
public function onChanged( $sender, $args )
{
echo( "'$args' added to user list\n" );
}
}
$ul = new UserList();
$ul->addObserver( new UserListLogger());
$ul->addCustomer( "Jack" );
示例2
使用了php中的 SplSubject,SplObserver 主體對象和觀察者對象類
// User.php 被觀察主體對象
<?php
namespace DesignPatterns\Behavioral\Observer;
/**
* 觀察者模式 : 被觀察對象 (主體對象)
*
* 主體對象維護觀察者列表并發(fā)送通知
*
*/
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 // 觀察者
namespace DesignPatterns\Behavioral\Observer;
/**
* UserObserver 類(觀察者對象)
*/
class UserObserver implements \SplObserver
{
/**
* 觀察者要實現(xiàn)的唯一方法
* 也是被 Subject 調(diào)用的方法
*
* @param \SplSubject $subject
*/
public function update(\SplSubject $subject)
{
echo get_class($subject) . ' has been updated';
}
}
// 測試
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() 調(diào)用
*/
public function testUpdateCalling()
{
$subject = new User();
$observer = $this->getMock('SplObserver');
$subject->attach($observer);
$observer->expects($this->once())
->method('update')
->with($subject);
$subject->notify();
}
}
官方demo
使用了SplObjectStorage
這個對象弧腥,這個對象本身有attach ,detach 的方法,如果不用潮太,可以參考第二個
class MyObserver1 implements SplObserver {
public function update(SplSubject $subject) {
echo __CLASS__ . ' - ' . $subject->getName();
}
}
class MyObserver2 implements SplObserver {
public function update(SplSubject $subject) {
echo __CLASS__ . ' - ' . $subject->getName();
}
}
class MySubject implements SplSubject {
private $_observers;
private $_name;
public function __construct($name) {
$this->_observers = new SplObjectStorage();
$this->_name = $name;
}
public function attach(SplObserver $observer) {
$this->_observers->attach($observer);
}
public function detach(SplObserver $observer) {
$this->_observers->detach($observer);
}
public function notify() {
foreach ($this->_observers as $observer) {
$observer->update($this);
}
}
public function getName() {
return $this->_name;
}
}
$observer1 = new MyObserver1();
$observer2 = new MyObserver2();
$subject = new MySubject("test");
$subject->attach($observer1);
$subject->attach($observer2);
$subject->notify();
一個報紙和訂閱者的例子管搪,很像發(fā)布和訂閱
/**
* Subject,that who makes news
*/
class Newspaper implements \SplSubject{
private $name;
private $observers = array();
private $content;
public function __construct($name) {
$this->name = $name;
}
//add observer
public function attach(\SplObserver $observer) {
$this->observers[] = $observer;
}
//remove observer
public function detach(\SplObserver $observer) {
$key = array_search($observer,$this->observers, true);
if($key){
unset($this->observers[$key]);
}
}
//set breakouts news
public function breakOutNews($content) {
$this->content = $content;
$this->notify();
}
public function getContent() {
return $this->content." ({$this->name})";
}
//notify observers(or some of them)
public function notify() {
foreach ($this->observers as $value) {
$value->update($this);
}
}
}
/**
* Observer,that who recieves news
*/
class Reader implements SplObserver{
private $name;
public function __construct($name) {
$this->name = $name;
}
public function update(\SplSubject $subject) {
echo $this->name.' is reading breakout news <b>'.$subject->getContent().'</b><br>';
}
}
$newspaper = new Newspaper('Newyork Times');
$allen = new Reader('Allen');
$jim = new Reader('Jim');
$linda = new Reader('Linda');
//add reader
$newspaper->attach($allen);
$newspaper->attach($jim);
$newspaper->attach($linda);
//remove reader
$newspaper->detach($linda);
//set break outs
$newspaper->breakOutNews('USA break down!');