观察者模式
观察者模式又称发布订阅模式,我们常用的
redis
、rabbitmq
和kafka
等都支持发布订阅,那么这个模式是怎么回事儿呢?
观察者模式主要有通知者和观察者等角色,观察者一般有多个。
我们看下面代码示例
<?php
/**
* Class Controller
* @datetime 2020/7/17 6:46 PM
* @author roach
* @email jhq0113@163.com
*/
class Controller
{
/**
* @var string
* @datetime 2020/7/17 6:36 PM
* @author roach
* @email jhq0113@163.com
*/
public $id;
/**
* @var string
* @datetime 2020/7/17 6:39 PM
* @author roach
* @email jhq0113@163.com
*/
public $actionId;
/**
* @datetime 2020/7/17 6:46 PM
* @author roach
* @email jhq0113@163.com
*/
public function init()
{
$userId = time();
$this->checkAccess($userId);
$this->trafficLimit();
}
/**鉴权
* @param int $userId
* @return int
* @datetime 2020/7/17 6:41 PM
* @author roach
* @email jhq0113@163.com
*/
public function checkAccess($userId)
{
return $userId & 1=== 1;
}
/**
* @return bool
* @datetime 2020/7/17 6:45 PM
* @author roach
* @email jhq0113@163.com
*/
public function trafficLimit()
{
$ip = $_SERVER['REMOTE_ADDR'];
/**
* @var \Redis $redis
*/
$multi = $redis->multi(\Redis::PIPELINE);
$limitKey = 'limit:'.$ip;
$multi->incr($limitKey);
$multi->expire($limitKey, 60);
$result = $multi->exec();
return $result[0] > 100;
}
}
以上代码是一个控制器基类,当有请求来临时会优先调用控制器的
init
方法,init
方法会主动调用checkAccess
和trafficLimit
方法,一个用于鉴权,一个用于限速,这段代码在我们日常项目中也是非常常见,那么有什么问题呢?
现在我们通过开闭原则进行分析,当我们修改限速逻辑,我们需要修改
trafficLimit
方法,Controller
发生了变化,没有对修改关闭;当我们增加其他调用时,init
方法需要发生修改,没有对扩展开放,那么我们怎么修改呢?
<?php
/**
* Interface IFilter
* @datetime 2020/7/17 6:52 PM
* @author roach
* @email jhq0113@163.com
*/
interface IFilter
{
/**
* @param array $params
* @return mixed
* @datetime 2020/7/17 6:51 PM
* @author roach
* @email jhq0113@163.com
*/
public function filter($params = []);
}
/**
* Class AccessFilter
* @datetime 2020/7/17 6:52 PM
* @author roach
* @email jhq0113@163.com
*/
class AccessFilter implements IFilter
{
/**
* @param array $params
* @return int|mixed
* @datetime 2020/7/17 6:52 PM
* @author roach
* @email jhq0113@163.com
*/
public function filter($params = [])
{
return $params['userId'] & 1=== 1;
}
}
/**
* Class LimitFilter
* @datetime 2020/7/17 6:53 PM
* @author roach
* @email jhq0113@163.com
*/
class LimitFilter implements IFilter
{
/**
* @param array $params
* @return bool|mixed
* @datetime 2020/7/17 6:53 PM
* @author roach
* @email jhq0113@163.com
*/
public function filter($params = [])
{
$ip = $_SERVER['REMOTE_ADDR'];
/**
* @var \Redis $redis
*/
$multi = $redis->multi(\Redis::PIPELINE);
$limitKey = 'limit:'.$ip;
$multi->incr($limitKey);
$multi->expire($limitKey, 60);
$result = $multi->exec();
return $result[0] > 100;
}
}
/**
* Class Controller
* @datetime 2020/7/17 6:46 PM
* @author roach
* @email jhq0113@163.com
*/
class Controller
{
/**
* @var string
* @datetime 2020/7/17 6:36 PM
* @author roach
* @email jhq0113@163.com
*/
public $id;
/**
* @var string
* @datetime 2020/7/17 6:39 PM
* @author roach
* @email jhq0113@163.com
*/
public $actionId;
/**
* @var array
* @datetime 2020/7/17 6:51 PM
* @author roach
* @email jhq0113@163.com
*/
protected $_filters = [];
/**
* @datetime 2020/7/17 6:46 PM
* @author roach
* @email jhq0113@163.com
*/
public function init()
{
$this->notify();
}
/**
* @param IFilter $filter
* @datetime 2020/7/17 6:55 PM
* @author roach
* @email jhq0113@163.com
*/
public function addFilter(IFilter $filter)
{
array_push($this->_filters, $filter);
}
/**
* @datetime 2020/7/17 6:55 PM
* @author roach
* @email jhq0113@163.com
*/
public function notify()
{
$params = [
'userId' => time()
];
foreach ($this->_filters as $filter) {
/**
* @var IFilter $filter
*/
$filter->filter($params);
}
}
}
修改之后,我们增加了
IFilter
接口(即抽象观察者角色),增加了AccessFilter
(即具体观察者角色)和LimitFilter
(即具体观察者角色)两个实现类,在控制器(即通知者角色)中增加了_filters
属性,控制器可以通过调用addFilter
方法增加观察者,通过调用notify
方法通知所有观察者。
我们来分析一下修改后的优点,当我们要修改鉴权或者限速的逻辑时,控制器的代码是不需要发生修改的,属于对修改关闭;当我们要增加新的
filter
时,增加一个类就可以了,控制器的代码也不用发生修改,属于对扩展开放。
一个对象必须通知其他对象,而并不知道这些对象是谁。需要在系统中创建一个触发链,这时我们就可以使用观察者模式来解决。
作者开源了一个事件的实现,封装在了
jhq0113/roach
中,我们可以通过composer
方式安装,代码开源地址如下
https://github.com/jhq0113/roach
jhq0113/roach
使用简单,代码精简,整个代码库纯代码大小为60K,composer
安装方式
composer require jhq0113/roach