设计模式实践--观察者模式

介绍

观察者模式,有时又被称为发布(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 协议》,转载必须注明作者和本文链接
本帖由系统于 3年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!