php设计模式(4) 观察者模式(附加 不听话的观察者)
2018-08-17 本文已影响0人
小杨不是小羊
php设计模式(4) 观察者模式(附加 不听话的观察者)
概述
观察者模式要求每个观察者都实现指定的接口,但是如果有其他原因导致不能直接注册,这时候我们应该使用反向注册
代码实现
目标 和 观察者(使用了 php spl 提供的接口)
<?php
interface SplSubject{
public function attach(SplObserver $observer);//注册观察者
public function detach(SplObserver $observer);//释放观察者
public function notify();//通知所有注册的观察者
}
interface SplObserver{
public function update(SplSubject $subject);//观察者进行更新状态
}
具体目标(被观察者)
class News implements SplSubject
{
public $pid = 0;
public $observers = [];
public function __construct($pid)
{
$this->pid = $pid;
/**
* 实现php 自动加载
*/
spl_autoload_register([$this, 'loadRegister']);
/**
* 这里是加载 Plug 目录下 所有插件
*/
$this->autoRegister();
}
/**
* 实现具体自动加载步骤
*
* 这里没有 遵循 psr 规范 强烈不建议
*
* 我这么写只是方便
*
* @param $class_name
*/
public function loadRegister($class_name)
{
$class_name = str_replace("\\", "/", $class_name);
include dirname(__DIR__) . '/' . $class_name . '.php';
}
/**
* 加载插件方法
*
* 这里是为了方便 一次性加载 Plug 下所有插件
*
* 并注册观察者
*/
public function autoRegister()
{
$path = __DIR__ . "/plug/";
$directory = dir($path);
while ($file = $directory->read()) {
if ($file == '.' || $file == "..") {
continue;
}
$class_name = basename($file, '.php');//NewsClick
/**
* 这里是因为有命名空间 所以要这么写 完全限定名称
*
* 关于更多命名空间直接参考手册 很详细
*/
$str = '\\News\\Plug\\' . $class_name;
// 在实例化的时候 直接把当前类传给要实例的目标 如果目标需要可以自行注册
$object = new $str($this);
//判断是否是 SplObserver 的子类 如果是直接注册为观察者
if (is_subclass_of($object, 'SplObserver', true)) {
$this->attach($object);
}
}
}
/**
* 添加一个观察者
*
* @param \SplObserver $observer
*/
public function attach(SplObserver $observer)
{
$this->observers[strval($observer)] = $observer;
}
/**
* 删除一个观察者
*
* @param \SplObserver $observer
*/
public function detach(SplObserver $observer)
{
if (array_search($observer, $this->observers, true)) {
unset($this->observers[strval($observer)]);
}
}
/**
* 当状态改变时 通知所有观察者
*/
public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
/**
* 相当于改变状态的方法 改变状态后调用 notify
*/
public function read()
{
echo "阅读了 product id " . $this->pid . " 的文章";
$this->notify();
}
}
具体观察者
// 这类继承 SplObserver 目标(被观察者)会直接将其注册为观察者
class NewsClick implements SplObserver
{
/**
* 接收到通知后你做什么由你来决定
*
* @param \SplSubject $subject
*/
public function update(SplSubject $subject)
{
echo "<hr>";
echo 'product ID' . $subject->pid . '点击量加1';
}
/**
* 这个模式方法让我用了做 key 了 你随意
*
* @return string
*/
public function __toString()
{
return "news_click";
}
}
// 这个类没有继承 SplObserver 所以需要自己来添加观察者
class NewsLike
{
public function __construct(SplSubject $news)
{
$news->attach(new class implements SplObserver
{
/**
* 接收到通知后你做什么由你来决定
*
* @param \SplSubject $subject
*/
public function update(SplSubject $subject)
{
echo "<hr>";
echo 'product ID' . $subject->pid . '收藏量加1';
}
/**
* 这个模式方法让我用了做 key 了 你随意
*
* @return string
*/
public function __toString()
{
return "news_like";
}
});
}
}
调用
//因为使用了自动加载 代码变少了
use news\News;
$news = new News(100);
$news->read();
结果
阅读了 product id 100 的文章<hr>product ID100收藏量加1<hr>product ID100点击量加1<hr>product ID100日志加1