预热
任何架构都要服务于特定业务场景,一切脱离业务谈架构都是耍流氓,所以我们举个简单的需求:客户可以发起存款,币种可以有人名币或者欧元。代码如下。
//存款抽象类,对子类进行约束
abstract class SaveHandle {
protected abstract function save(Request $request);
}
//人名币存款处理者
class RmbSaveHandle extends SaveHandle {
public function save(Request $request) {
echo '人名币存款处理';
}
}
//欧元存款处理者
class EuroSaveHandleextends SaveHandle {
public function save(Request $request) {
echo '欧元存款处理';
}
}
//请求类
class Request{
public $type; //存款类型 1人名币 2欧元
public $money; //存款的金额
public $who;
public function __construct($config = []) {
foreach ($config as $k => $v){
$this->$k = $v;
}
}
}
下面来看看场景类
class Client {
public static function main() {
//随机生成几个存款请求
$requests = [];
for ($i = 1; $i <= 5; $i++) {
$requests[] = new Request([
'who' => "adele {$i}",
'money' => 140000,
'type' => mt_rand(1, 2), //存款类型 1人名币 2欧元
]);
}
$rmb = new RmbSaveHandle();
$euro = new EuroSaveHandle();
foreach ($requests as $request){
if($request->type == 1){
//存的是人名币
$rmb->save($request);
}elseif($request->type == 2){
//存的是欧元
$euro->save($request);
}
}
}
}
现在整个过程已经完整的表现出来了但是你是不是发现这个程序写得有点不舒服? 有点别扭? 有点想重构它的感觉? 那就对了! 这段代码有以下几个问题:
- 代码臃肿
我们在Client类中写了if…else的判断条件, 而且能随着能处理该类型的请示人员越多, if…else的判断就越多, 想想看, 臃肿的条件判断还怎么有可读性? - 耦合过重
这是什么意思呢, 我们要根据Request的type来决定使用SaveHandler的那个实现类来处理请求。 有一个问题是: 如果SaveHandler的实现类继续扩展怎么办? 修改Client类? 与开闭原则违背了! - 职责界定不清晰
对客户发起的人名币存款请求,RmbSaveHandle有责任、 有义务处理请求。因此RmbSaveHandle类应该是知道客户的请求自己处理, 而不是在Client类中进行组装出来, 也就是说原本应该是RmbSaveHandle这个类做的事情抛给了其他类进行处理, 不应该是这样的。
既然有这么多的问题, 那我们要想办法来解决这些问题, 我们先来分析一下需求。客户发起一笔存款,必然要获得一个答复, 甭管是成功还是失败, 总之是要一个答复的, 而且这个答复是唯一的,OK, 分析完毕, 收工, 重新设计, 我们可以抽象成这样一个结构, 客户的请求先发送到人名币处理类, 人名币处理类一看是自己要处理的, 就作出回应处理, 如果存款类型是欧元, 那就要把这个请求转发到欧元处理者。看如下顺序图1。
人名币、欧元每个节点有两个选择: 要么承担责任, 做出回应; 要么把请求转发到后序环节。 结构分析得已经很清楚了, 那我们看怎么来实现这个功能,重构一下我们的代码。
//存款抽象类
abstract class SaveHandle {
const TYPE_RMB = 1;
const TYPE_EURO = 2;
/* @var SaveHandle*/
protected $next;
//判断自己是否有权力处理
protected abstract function checkCondition(Request $request);
//真正的处理逻辑
protected abstract function save(Request $request);
//接受请求
public function handleMessage(Request $request){
if($this->checkCondition($request)){
//有权限处理
return $this->save($request);
}else{
//没有权限处理
if($this->next){
//交给下一位处理者
return $this->next->handleMessage($request);
}else{
return '不处理';
}
}
}
public function setNext(SaveHandle $next){
$this->next = $next;
return $next;
}
}
//欧元存款处理者
class EuroSaveHandle extends SaveHandle {
protected function checkCondition(Request $request) {
return $request->type == self::TYPE_EURO;
}
protected function save(Request $request) {
echo '欧元存款处理';
}
}
//人名币存款处理者
class RmbSaveHandle extends SaveHandle {
protected function checkCondition(Request $request) {
return $request->type == self::TYPE_RMB;
}
public function save(Request $request) {
echo '人名币存款处理';
}
}
抽象类的方法比较长,但是还是比较简单的, 读者有没有看到, 其实在这里也用到模板方法模式, 在模板方法中判断请求的级别和当前能够处理的级别, 如果相同则调用基本方法, 做出反馈; 如果不相等, 则传递到下一个环节, 由下一环节做出回应, 如果已经达到环节结尾,则直接做不做处理。 每个实现类只要实现两个职责: 一是定义自己能够处理的等级级别; 二是对请求做出回应。下面看看我们的场景类。
class Client {
public static function main() {
//随机生成几个存款请求
$requests = [];
for ($i = 1; $i <= 5; $i++) {
$requests[] = new Request([
'who' => "adele {$i}",
'money' => 140000,
'type' => mt_rand(1, 2), //存款类型 1人名币 2欧元
]);
}
//设置处理的顺序 RMB->EURO
$rmb = new RmbSaveHandle();
$euro = new EuroSaveHandle();
$rmb->setNext($euro);
foreach ($requests as $request){
$rmb->handleMessage($request);
}
}
}
在Client中设置请求的传递顺序, 先把请求交给人名币处理者, 不是人名币处理者应该解决的问题, 则由人名币处理者传递到欧元处理者解决。业务调用类Client也不用去做判断到底是需要谁去处理, 而且SaveHandler抽象类的子类可以继续增加下去, 只需要扩展传递链而已, 调用类可以不用了解变化过程, 甚至是谁在处理这个请求都不用知道。 这就是责任链模式。
定义
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理请求为止。责任链模式的重点是在“链”上, 由一条链去处理相似的请求在链中决定谁来处理这个请求, 并返回相应的结果。
抽象的处理者实现三个职责: 一是定义一个请求的处理方法handleMessage, 唯一对外开放的方法; 二是定义一个链的编排方法setNext, 设置下一个处理者; 三是定义了具体的请求者必须实现的两个方法: 定义自己能够处理的级别checkCondition和具体的处理任务save。
更完美的封装
上面举的例子中只对请求进行了封装,实际应用中可以对请求的处理级别进行封装。对处理结果的返回也进行封装。
class Level {
//定义一个请求和处理等级
}
class Request {
//请求的等级
public getRequestLevel(){
return null;
}
}
class Response {
//处理者返回的数据
}
在实际应用中, 一般会有一个封装类对责任模式进行封装, 也就是替代Client类, 直接返回链中的第一个处理者, 具体链的设置不需要高层次模块关系, 这样, 更简化了高层次模块的调用, 减少模块间的耦合, 提高系统的灵活性。
优点
责任链模式非常显著的优点是将请求和处理分开。 请求者可以不用知道是谁处理的, 处理者可以不用知道请求的全貌 , 两者解耦, 提高系统的灵活性。
缺点
责任链有两个非常显著的缺点: 一是性能问题, 每个请求都是从链头遍历到链尾, 特别是在链比较长的时候, 性能是一个非常大的问题。 二是调试不很方便, 特别是链条比较长,环节比较多的时候, 由于采用了类似递归的方式, 调试的时候逻辑可能比较复杂。