觀察者模式(observer pattern)

觀察者模式有時也被稱作發(fā)布/訂閱模式灵奖,該模式用于為對象實現(xiàn)發(fā)布/訂閱功能:一旦主體對象狀態(tài)發(fā)生改變卷玉,與之關(guān)聯(lián)的觀察者對象會收到通知,并進行相應操作宁昭。

將一個系統(tǒng)分割成一個一些類相互協(xié)作的類有一個不好的副作用跌宛,那就是需要維護相關(guān)對象間的一致性。我們不希望為了維持一致性而使各類緊密耦合积仗,這樣會給維護疆拘、擴展和重用都帶來不便。觀察者就是解決這類的耦合關(guān)系的寂曹。

消息隊列系統(tǒng)哎迄、事件都使用了觀察者模式。

PHP 為觀察者模式定義了兩個接口:SplSubjectSplObserver隆圆。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!');

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市消别,隨后出現(xiàn)的幾起案子抛蚤,更是在濱河造成了極大的恐慌台谢,老刑警劉巖寻狂,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異朋沮,居然都是意外死亡蛇券,警方通過查閱死者的電腦和手機缀壤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纠亚,“玉大人塘慕,你說我怎么就攤上這事〉侔” “怎么了图呢?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長骗随。 經(jīng)常有香客問我蛤织,道長,這世上最難降的妖魔是什么鸿染? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任指蚜,我火速辦了婚禮,結(jié)果婚禮上涨椒,老公的妹妹穿的比我還像新娘摊鸡。我一直安慰自己,他們只是感情好蚕冬,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布免猾。 她就那樣靜靜地躺著,像睡著了一般播瞳。 火紅的嫁衣襯著肌膚如雪掸刊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天赢乓,我揣著相機與錄音忧侧,去河邊找鬼。 笑死牌芋,一個胖子當著我的面吹牛蚓炬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播躺屁,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肯夏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了犀暑?” 一聲冷哼從身側(cè)響起驯击,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耐亏,沒想到半個月后徊都,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡广辰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年暇矫,在試婚紗的時候發(fā)現(xiàn)自己被綠了主之。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡李根,死狀恐怖槽奕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情房轿,我是刑警寧澤粤攒,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站囱持,受9級特大地震影響琼讽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洪唐,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一钻蹬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凭需,春花似錦问欠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枯怖,卻和暖如春注整,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背度硝。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工肿轨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕊程。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓椒袍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藻茂。 傳聞我的和親對象是個殘疾皇子驹暑,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容