💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## **观察者模式** >[info] 当一个对象状态发生改变的时候,依赖他的对象全部会收到通知,并且自动更新等等一连串的操作, 将这些一连串的操作(的逻辑代码)放到观察者列表里 >场景:一个事件发生后,要执行一连串的更新操作,传统的编程方法,就是在事件的代码之后直接加入逻辑处理,当更新的逻辑增多之后,代码会变得难以维护。这种方式是耦合的,侵入式的。增加新的逻辑需要修改事件主体的代码 察者模式实现了低耦合,非侵入式的通知与更新机制 >[danger]状态触发类的应用当你的程序要处理的对象有很多种状态,就可以考虑使用这个模式 如果想在框架任何一个地方里插入其他逻辑二无需大的改动,请用观察者模式,只需在需要的地方添加一段触发观察的代码就可以 ## **业务场景:** **窗体程序设计中的事件处理**-- 窗体中的所有组件都是“事件源”,也就是目标对象,而事件处理程序类的对象是具体观察者对象 **注册**-- 一般和活动挂钩如注册送金币和积分也可以送给推荐人 **下订单**--下订单就多了 活动是最起码的 其次 比如发短信发邮件存入日志 **插件** -- 比如许多框架的第三方插件就是这个原理 **订阅功能** --简单的是一些第三方的支付接入或者其他功能接入,他们都暴露一个订阅功能,你可以选择订阅,然后你就作为观察者,每次第三方有更新和通知的时候,所有订阅的人,都能收到更新通知了 ## **模式的应用场景** 1. 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。 2. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。 ## **模式的结构** 1. **抽象主题(Subject)角色**:也叫抽象目标类,它提供了一个用于**保存**观察者对象的聚集类和**增加**、**删除**观察者对象的方法,以及**通知**所有观察者的抽象方法。 2. **具体主题(Concrete    Subject)角色**:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。 3. **抽象观察者(Observer)角色**:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。 4. **具体观察者(Concrete Observer)角色**:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。 ![](https://img.kancloud.cn/9b/b5/9bb5f48938374190f51ee96ff500987d_725x474.png) ``` //抽象目标 abstract class Subject { protected $_observers=array(); //增加观察者方法 public function add(Observer $observer) { $this->_observers[]=$observer; } //删除观察者方法 public function remove(Observer $observer) { foreach($this->_observers as $index => $observer) { if ($observer === $observer) { array_splice($this->_observers, $index, 1); return; } } } //通知观察者方法 public abstract function notifyObserver(); } //具体目标 class ConcreteSubject extends Subject { public function notifyObserver() { print_r("具体目标发生改变...<br>"); print_r("--------------<br>"); foreach ($this->_observers as $observer) { $observer->response(); } } } //抽象观察者 interface Observer { function response(); //反应 } //具体观察者1 class ConcreteObserver1 implements Observer { public function response() { print_r("具体观察者1作出反应!"); } } //具体观察者1 class ConcreteObserver2 implements Observer { public function response() { print_r("具体观察者2作出反应!"); } } class ObserverPattern { public static function main() { $subject=new ConcreteSubject(); $subject->add(new ConcreteObserver1()); $subject->add(new ConcreteObserver2()); $subject->notifyObserver(); } } ObserverPattern::main(); ``` 例子: ``` //抽象主题角色 interface ISubject{ function addObserver( $observer ); } // 具体主题角色(被观察者) 用于添加、删除、保存、通知观察者 class UserList implements ISubject{ private $_observers = array(); public function sendMsg( $name ){ foreach( $this->_observers as $obs ){ $obs->onSendMsg( $this, $name ); } } public function addObserver( $observer ){ $this->_observers[]= $observer; } public function removeObserver($observer_name) { foreach($this->_observers as $index => $observer) { if ($observer->getName() === $observer_name) { array_splice($this->_observers, $index, 1); return; } } } } //抽象观察者 interface IObserver{ function onSendMsg( $sender, $args ); function getName(); } //具体观察者角色1 class UserListLogger implements IObserver{ public function onSendMsg( $sender, $args ){ echo( "'$args' 以发送到 UserListLogger\n" ); } public function getName(){ return static::class;//同 return 'UserListLogger'; } } //具体观察者角色2 class OtherObserver implements IObserver{ public function onSendMsg( $sender, $args ){ echo( "'$args' 已发送到 OtherObserver\n" ); } public function getName(){ return static::class;//同 return 'OtherObserver'; } } $ul = new UserList();//被观察者 $ul->addObserver( new UserListLogger() );//增加观察者 $ul->addObserver( new OtherObserver() );//增加观察者 $ul->sendMsg( "Jack" );//发送消息到观察者 echo "<br>"; $ul->removeObserver('UserListLogger');//移除观察者 $ul->sendMsg("hello");//发送消息到观察者 ``` ## **例子** php 内置了SplSubject与SplObserver接口可以更简便的实现观察者模式 >[info]newspaper是被观察的对象,reader是观察者。当报纸发布消息, 每一个用户都会得到通知。这就是观察者模式的使用场景 ~~~ class Newspaper implements SplSubject { private $name; private $observers; private $content; public function __construct($name){ $this->name = $name; $this->observers = new SplObjectStorage(); } //增加一个观察者 public function attach(SplObserver $observer){ //向SplObjectStorage中添加一个对象 //此处为SplObjectStorage对象存储类的attach方法而SplSubject 的 $this->observers->attach($observer); } //移除一个观察者 public function detach(SplObserver $observer){ $this->observers->detach($observer); } //通知所有观察者 public function notify(){ foreach ($this->observers as $observer) { $observer->update($this); } } public function getContent(){ return $this->content."{$this->name}"; } public function breakOutNews($content) { $this->content = $content; $this->notify(); } } <?php class Reader implements SplObserver { private $name; public function __construct($name){ $this->name = $name; } public function update(SplSubject $subject) { echo $this->name.' is reading breakout news'.$subject->getContent(); } } <?php include "Newspaper.php"; include "Reader.php"; class WorkFlow { public function run() { $newspaper = new Newspaper('纽约时报');//SplSubject $allen = new Reader("allen");//SplObserver $jimmy = new Reader("jimmy");//SplObserver $tom = new Reader("tom");//SplObserver $newspaper->attach($allen);// $newspaper->attach($jimmy); $newspaper->attach($tom); $newspaper->detach($tom); $newspaper->breakOutNews('USA BREAK DOWN'); } } $work = new WorkFlow(); $work->run(); ~~~ ## **php中的钩子(hook插件机制)** 插件管理器类作为具体主题(被观察者)角色:也叫具体目标类,用于保存、添加、删除及通知观察者(目的是执行入口函数) 插件作为观察者,一旦被注册则调用插件的入口函数 >[warning] /index.php ``` <?php define('ROOTPATH',__DIR__.DIRECTORY_SEPARATOR); /** * 获取激活的插件 * @return array 返回激活的插件信息数组 */ function get_active_plugins(){ //一般从数据库拉出 这里用数据模拟 $plugins=[ [ 'name' => 'demo', 'directory'=>'demo' ], ]; return $plugins; } require_once "./core/PluginManager.php"; $pluginManager=new PluginManager(); //trigger通知观察者(插件) $pluginManager->trigger('demo',''); ``` >[warning] 插件管理类 /core/PluginManager.php ``` <?php class PluginManager{ private $_listeners=array(); public function __construct(){ $this->_init(); } private function _init(){ //外部获取所有活动插件,返回包含插件名及插件目录的数组 $plugins=get_active_plugins(); //循环实例激活 foreach ($plugins as $plugin) { $plugin_file=ROOTPATH.'plugins'.DIRECTORY_SEPARATOR.$plugin['directory'].DIRECTORY_SEPARATOR.'actions.php'; if (@file_exists($plugin_file)) { //加载这些插件的文件 include_once($plugin_file); }else{ throw new Exception('文件'.$plugin_file.'不存在!'); } //解析出这些插件的插件类名 $class = strtolower($plugin['name']).'_actions';//如demo_actions if(class_exists($class)){ //实例化插件类 同时实例化的插件析构方法调用register注册插件(将实例放入$_listeners中) //等同 new demo_actions(PluginManager $this); $pluginManager->register('demo', $demo_actions_instance, 'say_hello'); new $class($this); }else{ throw new Exception($class.'不存在!'); } } } /** * 触发一个钩子 * * @param string $hook 钩子的名称 * @param mixed $data 钩子的入参 * @return mixed */ public function trigger($hook,$data){ $result = ''; if (isset($this->_listeners[$hook]) && is_array($this->_listeners[$hook]) && count($this->_listeners[$hook]) > 0){ var_dump($this->_listeners); foreach ($this->_listeners[$hook] as $listener) { //他的key就是 demo_actions->say_hello // 取出插件对象的引用和方法 $class =& $listener[0]; $method = $listener[1]; if(method_exists($class,$method)) { // 动态调用插件的方法 $result .= $class->$method($data); } } } return $result; } /** * 注册插件 * @param string $hook 钩子的名称 * @param obj &$reference 插件类实例的引用 * @param string $method 插件所执行的方法(插件入口方法) * @return void */ public function register($hook, &$reference, $method){ //获取插件要实现的方法 $key = get_class($reference).'->'.$method; //将插件的引用连同方法push进监听数组即$_listeners属性中 $this->_listeners[$hook][$key] = array(&$reference, $method); #此处做些日志记录方面的东西 } } ``` >[warning] /plugins/demo/actions.php ~~~ <?php /** *需要注意的几个默认规则: * 1. 本插件类的文件名必须是action * 2. 插件类的名称必须是{插件名_actions} */ class demo_actions { //解析函数的参数是pluginManager的引用 function __construct(PluginManager &$pluginManager) { //注册这个插件 //第一个参数是钩子的名称 //第二个参数是该类实例的引用 //第三个是插件所执行的方法 $pluginManager->register('demo', $this, 'say_hello'); } function say_hello() { echo 'Hello World'; } } ~~~ ## [php插件机制原理之其他实现方式例子](https://www.jianshu.com/p/7747bbbd14c0)