設計模式實踐--觀察者模式

zhaohehe發表於2017-01-24

介紹

觀察者模式,有時又被稱為釋出(publish )-訂閱(Subscribe)模式。是軟體設計模式的一種。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實現事件處理系統。

典型的觀察者模式中會有兩個角色,觀察者和被觀察者

被觀察者

被觀察物件發生了某種變化時,會從存有自己觀察者的陣列中得到所有註冊過的觀察者,然後呼叫觀察者的指定方法,將變化通知觀察者。

<?php
class Paper
{ 
    private $observers = [];

    /*  註冊觀察者 */
    public function register($sub)
    { 
        $this->observers[] = $sub;
    }

    /*  外部統一訪問    */
    public function trigger()
    { 
        if(! empty($this->observers)) {
            foreach($this->observers as $observer) {
                $observer->update();    //通知變化給觀察者
            }
        }
    }
}

觀察者

(Observer)將自己註冊到被觀察物件(Subject)中,被觀察物件將觀察者儲存在自己類中的一個陣列中。

<?php
/**
 * 觀察者要實現的介面
 */
interface Observerable
{
    public function update();
}
 <?php
class Subscriber implements Observerable
{
    public function update(){
        echo "do something";
    }
}

然後在某個地方將觀察者註冊到被觀察者上,需要的時候呼叫觀察者的trigger()方法,通知觀察者

<?php
$paper = new Paper();
$paper->register(new Subscriber());

$paper->trigger();

框架中的應用

Event

<?php
namespace App\Events;

class Event
{
    public $author;

    public function __construct()
    {
        $this->author = 'zhaohehe';
    }
}

在框架中,被觀察者叫做Event,即事件。然後event本身不再儲存自己的觀察者,而是將觀察者和被觀察者之間的繫結放在了EventServiceProvider中。

EventServiceProvider

<?php

namespace App\Providers;

use System\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        'App\Events\Event' => [
            'App\Listeners\Listener',
        ]
    ];

    public function boot()
    {
        parent::boot();
    }
}

上面的EventServiceProvider繼承自System\Support\Providers\EventServiceProvider,父類的程式碼如下:

<?php

namespace System\Support\Providers;

use System\Events\Dispatcher;
use System\Support\ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [];

    public function boot()
    {
        $Event = $this->app->make('events');

        foreach ($this->listens() as $event => $listeners) {
            foreach ($listeners as $listener) {
                $Event->listen($event, $listener);
            }
        }
    }

    public function register()
    {
    }

    public function listens()
    {
        return $this->listen;
    }
}

System\Support\Providers\EventServiceProvider父類用$listen屬性來從子類那裡拿到儲存有event和listener之間關係的陣列。(其實這也算是一個微小的常用設計模式:在父類中對某一個變數進行操作,然後由子類來定義這個變數。)然後父類會通過$this->app->make('events');從容器中拿到一個event例項,再用一個foreach迴圈通過event例項的listen方法來給每一個event註冊一個或多個listener。

Dispatcher

從容器中拿出來的event例項實際上是System\Foundation\Container\Dispatcher

 public function listen($event, $listener)
    {
        $this->listeners[$event][] = $this->makeListener($listener);

        unset($this->sorted[$event]);
    }

在listen方法中,Dispatcher用一個二維陣列$this->listeners來儲存每一個事件對應的觀察者,它的makeListener()方法會接收一個listener,最終返回的其實是一個匿名函式,當需要通知事件的觀察者時,Dispatcher會取出這個匿名函式,並執行通知,具體怎麼通知,接著往下看。

啟用event

$e = $app->make('events');

$e->fire(new App\Events\Event());

以上的兩行程式碼表示從容器中取得event例項,然後呼叫它的fire()方法,並傳入你想要啟用的event物件,前面說過event例項實質上是一個Dispatcher物件,這個時候就需要看Dispatcher類中fire()方法是怎麼將事件通知給該事件的listener的。

 public function fire($event, $payload = [])
 {
        ...
        foreach ($this->getListeners($event) as $listener) {
            $response = call_user_func_array($listener, $payload);

            if (! is_null($response)) {
                array_pop($this->firing);

                return $response;
            }
        ...
    }

可以看到,第n行程式碼呼叫call_user_func_array,執行listener物件的handle方法,並且將繫結的event作為引數傳遞過去,你可以去原始碼中看看$listener變數到底是什麼,其實它是一個有兩個元素陣列,第一個元素是listener物件,第二個元素是呼叫的方法,預設是handle,你也可以在serviceProvider中自定義。

listener

<?php

namespace App\Listeners;

use App\Events\Event;

class Listener
{
    public function __construct()
    {
    }

    public function handle(Event $event)
    {
        var_dump($event->author);
    }
}

以上,就完成了一個事件從註冊它的觀察者,到觸發時間,通知觀察者的過程。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章