博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
PHP设计模式之——观察者模式
阅读量:4294 次
发布时间:2019-05-27

本文共 6317 字,大约阅读时间需要 21 分钟。

今天来介绍一个很有用的设计模式,“观察者模式”,顾名思义,既然有“观察者”, 那么一定就有“被观察者”,从这个层面可以理解到,在这个设计模式中有两个对象, 一个是主体对象,一个是客体对象,在实际的代码实现上实际是“被观察者”主动通知了“观察者”。“被观察者”是主体对象,现在看不懂没关系,把下面的代码抄几遍就懂了。

PHP 提供了两个内置接口来帮助实现“观察者模式”, 其中“被观察者”需要实现 SplSubject 接口,“观察者”需要实现SplOberver

来看一个例子:

user['name'].' is registering.'.PHP_EOL; }}class PluginB implements SplObserver { public function update(\SplSubject $subject) { echo "Plugin B ".$subject->user['name'].' is registering.'.PHP_EOL; }}class User implements SplSubject { private $observers = []; public $user=[]; public function register($name, $password){ $this->user['name'] = $name; $this->user['password'] = $password; $this->notify(); } public function attach(\SplObserver $observer) { array_push($this->observers, $observer); } public function detach(\SplObserver $observer) { $index = array_search($observer, $this->observers, true); if ($index){ unset($this->observers[$index]); } } public function notify(){ foreach ($this->observers as $observer) { $observer->update($this); } }}//-- 实例化主体对象$user = new User();//-- end//-- 注册插件$plugins = [ new PluginA(), new PluginB(),];foreach ($plugins as $plugin) { $user->attach($plugin);}//-- end//-- 执行主体的业务代码$user->register('shanhuhai', '123456');

将以上代码保存即可运行。

以上代码模拟了一个调用注册方法register时,通知相关插件执行相应任务的机制。它使用观察者模式实现了插件的机制。
插件对象PluginAPluginB的实例就是“观察者”,User类的实例 $user 是“被观察者”,“观察者”需要通过 attach 方法注册到“被关观察者” 上,

register 方法被时,User 会通过notify 方法来通知所有的插件,并将主体对象即被观察者传递给插件。

这里仅仅是通过最简单的代码展示了什么是观察者模式,如果想实现插件,我们应该能传递更多的信息给插件,比如我们想在插件中针对主体对象的发生不同事件(不同的方法被调用)做不同的处理,我们可能需要“事件”的概念,你知道如何实现么?

在实际的业务中,观察者应该对不同的情况做出不同的发应,这个其实很简单,我们加一个给主体对象加一个event的属性就ok了。

看对上一篇修改后的代码,可以自己对比下,改动并不大:

event == 'register') { echo "Plugin A ".$subject->user['name'].' is registering.'.PHP_EOL; } }}class PluginB implements SplObserver { public function update(\SplSubject $subject) { if($subject->event == 'register'){ echo "Plugin B ".$subject->user['name'].' is registering.'.PHP_EOL; } if($subject->event == 'login') { echo "Plugin B ".$subject->user['name']. ' is logining.'.PHP_EOL; } }}class User implements SplSubject { public $event; private $observers = []; public $user=[]; public function register($name, $password){ $this->user['name'] = $name; $this->user['password'] = $password; $this->event = 'register'; $this->notify(); } public function login($name, $password) { $this->user['name'] = $name; $this->user['password'] = $password; $this->event = 'login'; $this->notify(); } public function attach(\SplObserver $observer) { array_push($this->observers, $observer); } public function detach(\SplObserver $observer) { $index = array_search($observer, $this->observers, true); if ($index){ unset($this->observers[$index]); } } public function notify(){ foreach ($this->observers as $observer) { $observer->update($this); } }}//-- 实例化主体对象$user = new User();//-- end//-- 注册插件$plugins = [ new PluginA(), new PluginB(),];foreach ($plugins as $plugin) { $user->attach($plugin);}//-- end//-- 执行主体的业务代码$user->register('shanhuhai', '123456');$user->login('shanhuhai', '123456');

对比上一篇的代码,我们在notify之前都需要通过 $this->event='事件名',来指明本次notify 的事件是什么,这样在插件中就可以根据不同的事件做出相应的处理。

完成了这个之后你会发现一个问题,目前来看我必须将所有的插件实例化后才能注册到主体对象中,那么问题来了,如果一个插件,只处理部分事件,那么如果在整个程序处理过程中这个事件没有被触发,那么这个插件的实例化是不是浪费了?

为了解决这个问题,我们在下一篇会将代码做大幅改动,基本是为了在具体知道那个事件被触发了以后才去实例化相应的要处理这个事件的插件。

插件可以根据不同的事件来做不同的业务处理,基本上很简单,我们直接通过主体对象携带事件名称即可。

下面我们要实现的是,只有在知道具体事件后才去实例化绑定了此事件的插件。

下面是具体的实现:

event == 'register') { echo "Plugin A ".$subject->user['name'].' is registering.'.PHP_EOL; } }}class PluginB implements SplObserver { public function update(\SplSubject $subject) { if($subject->event == 'register'){ echo "Plugin B ".$subject->user['name'].' is registering.'.PHP_EOL; } if($subject->event == 'login') { echo "Plugin B ".$subject->user['name']. ' is logining.'.PHP_EOL; } }}class Observer implements SplObserver { private $config = []; public function __construct($config) { foreach ($config as $plugin => $events ) { foreach ($events as $event) { $this->config[$event][] = $plugin; } } } public function update(\SplSubject $subject) { if(array_key_exists($subject->event, $this->config)) { foreach ($this->config[$subject->event] as $plugin) { $plugin = new $plugin(); $plugin->update($subject); } } }}class User implements SplSubject { public $event; private $observers = []; public $user=[]; public function register($name, $password){ $this->user['name'] = $name; $this->user['password'] = $password; $this->event = 'register'; $this->notify(); } public function login($name, $password) { $this->user['name'] = $name; $this->user['password'] = $password; $this->event = 'login'; $this->notify(); } public function attach(\SplObserver $observer) { //array_push($this->observers, $observer); $this->observer = $observer; } public function detach(\SplObserver $observer) { $this->observer = null; } public function notify(){ $this->observer->update($this); }}//-- 实例化主体对象$user = new User();//-- end//-- 配置插件$config = [ 'PluginA' => ['register' ], 'PluginB' => ['register', 'login'],];//-- end//-- 注册插件$observer = new Observer($config);$user->attach($observer);//-- end//-- 执行主体的业务代码$user->register('shanhuhai', '123456');$user->login('shanhuhai', '123456');

跟上一篇的代码比较,我们增加了一个配置$config, 用来说明哪些插件类绑定了哪些事件。

还新增了一个类 Observer 实现了 SplObserver 接口,我们通过给这个类传入 $config 配置来绑定插件和事件的映射关系, 当在主体对象 User 中调用 notify 时,我们调用Observerupdate, 这时可以根据之前绑定的插件与事件的映射关系来实例化相应的插件处理事件。

其实观察者模式还叫“发布/订阅模式”, 这跟消息队列中说的“发布/订阅”基本上是一个意思,我们在 $config 的配置说明了订阅关系,哪个插件订阅哪个事件,当被订阅的事件发生时这个插件就会收到消息。比如 PluginA 订阅了 register 事件,当主体对象中 指定的event 等于 register时, PluginA将会被实例化并接收到主体对象传过来的数据。

在实际应用情况中,插件应该在一个特定目录中,每个插件是一个文件,以上机制想要应用到你的项目中,还要根据实际情况做相应修改。

转载地址:http://bruws.baihongyu.com/

你可能感兴趣的文章
获取推送通知的DeviceToken
查看>>
Could not find a storyboard named 'Main' in bundle NSBundle
查看>>
CocoaPods安装和使用教程
查看>>
Beginning Auto Layout Tutorial
查看>>
block使用小结、在arc中使用block、如何防止循环引用
查看>>
iPhone开发学习笔记002——Xib设计UITableViewCell然后动态加载
查看>>
iOS开发中遇到的问题整理 (一)
查看>>
Swift code into Object-C 出现 ***-swift have not found this file 的问题
查看>>
为什么你的App介绍写得像一坨翔?
查看>>
RTImageAssets插件--@3x可自动生成@2x图片
查看>>
iOS开发的一些奇巧淫技
查看>>
常浏览的博客和网站
查看>>
Xcode 工程文件打开不出来, cannot be opened because the project file cannot be parsed.
查看>>
点击button实现Storyboard中TabBar Controller的tab切换
查看>>
Xcode 的正确打开方式——Debugging
查看>>
打包app出现的一个问题
查看>>
iOS在Xcode6中怎么创建OC category文件
查看>>
Expanding User-Defined Runtime Attributes in Xcode with Objective-C
查看>>
iOS7 UITabBar自定义选中图片显示为默认蓝色的Bug
查看>>
提升UITableView性能-复杂页面的优化
查看>>