观察者模式常用于插件开发。
核心:当对象的状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。
举个列子
那图片举例。观察者是观众。被观察者是电影。当电影播放到一定程度(悲剧,喜剧等)。观众受到剧情的影响哈哈大笑或者悲伤。
被观察者所需要的基本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为:<b style='color:red'>{$news_id}的新闻被点击+1</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();