介紹
觀察者模式,有時又被稱為釋出(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 協議》,轉載必須註明作者和本文連結