观察者模式常用于插件开发。

核心:当对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。

举个列子

图

那图片举例。观察者是观众。被观察者是电影。当电影播放到一定程度(悲剧,喜剧等)。观众受到剧情的影响哈哈大笑或者悲伤。

被观察者所需要的基本API

  • 注册(也就是买票)。必须
  • 反注册(也就是退票)。
  • 通知(抖包袱,哭戏,控制观众的情绪)

观察者所需要的基本API

  • 接受通知的方法

以新闻举例的伪代码

代码目录结构

图

先定义INewsPlugin.php 接口。抽离出被观察者的对基本的3个API

namespace App\Observer\news;


interface INewsPlugin
{
     function registerPlugin(IPlugin $plugin);
     function unRegisterPlugin(IPlugin $plugin);
     function display();
}

在定义观察者的一个基本API

namespace App\Observer\news;


interface IPlugin
{
    function update($id);
}

观察者的Iplugins

namespace App\Observer\Iplugins;


use App\Observer\news\IPlugin;

class AgreePlugin implements IPlugin
{

    public function update($news_id="")
    {
        echo "<hr/><br/>";
        echo "新闻ID为:<b style='color:green;'>{$news_id}的新闻被点赞+1</b>、时间是".date('Y-m-d H:i:s');
    }
    public function __toString()
    {
        return "AgreePlugin";
    }
}

NewClickPlugin.php是假设第三方的定义的插件没有按照规定去做。修改依赖方式代码。

namespace App\Observer\Iplugins;


use App\Observer\news\INewsPlugin;
use App\Observer\news\IPlugin;

class NewClickPlugin
{
    public function __construct(INewsPlugin $news)
    {
        $news->registerPlugin(new class implements IPlugin{
            public function update($news_id="")
            {
                echo "<hr/><br/>";
                echo "新闻ID为:&lt;b style='color:red'>{$news_id}的新闻被点击+1&lt;/b>、时间是".date('Y-m-d H:i:s');
            }
            public function __toString()
            {
                return "NewClickPlugin";
            }
        });
    }
}

被观察者代码

namespace App\Observer\news;
use App\Observer\Iplugins;


class NewsDetail  implements INewsPlugin
{
    public $news_data;
    public $news_id=0;
    public $plugs=[];

    public function __construct($news_id)
    {
        $this->news_data=[
            ['news_id'=>101,'news_content'=>"乌拉拉无啦啦",'news_title'=>"我是标题101"],
            ['news_id'=>102,'news_content'=>"无啦啦乌拉拉",'news_title'=>"我是标题102"]
        ];
        $this->news_id=$news_id;
//        $plugin=new ClickPlugin();
//        $this->registerPlugin($plugin);
        $this->scanPlugin();
    }

    public function getNews()
    {
        foreach($this->news_data as $news)
        {
            if($news["news_id"]==$this->news_id)
                return $news;
        }
    }

    public function registerPlugin(IPlugin $plugin)
    {
       return $this->plugs[strval($plugin)]=$plugin;
    }

    public function unRegisterPlugin(IPlugin $plugin)
    {
        unset($this->plugs[strval($plugin)]);
    }

    public function display()
    {
        $get_news=$this->getNews();
        //下面的代码 是模拟假设加载了模板,根据数据输出内容
        echo "<h2>".$get_news["news_title"]."</h2>";
        echo "<div>".$get_news["news_content"]."</div>";


        //执行插件方法
        foreach($this->plugs as $plugName=>$plugObject)
        {
            $plugObject->update($this->news_id);
        }
    }

    /**
     * 被观察者扫描插件
     */
    public function scanPlugin()
    {
        //扫描插件
        $scanDir=__DIR__."/../Iplugins/";

        $dir=dir($scanDir);
        while($file=$dir->read())
        {
            if(is_file($scanDir.$file)) //做一个插件 验证机制
            {
                $className=basename($file,".php");
                $className="App\\Observer\\Iplugins\\".$className;
                $classObject=new $className($this);//如果不继承Iplugin 根据约定,由插件自己完成注册
                if($classObject instanceof IPlugin){
                    $this->registerPlugin($classObject); //被观察者来主动注册 插件
                }


            }
        }
    }
}

调用代码

require "Loader.php";
Loader::init();
use \App\Observer\news\NewsDetail;
$news_id=$_GET['news_id']?$_GET['news_id']:0;
$news=new NewsDetail($news_id);
$news->display();

echo PHP_EOL."<br/>".PHP_EOL;
echo "文件加载数:".Loader::getLoadFileNum();
Last modification:February 1, 2020
如果觉得我的文章对你有用,请随意赞赏