责任链模式的英文翻译是 Chain Of Responsibility Design Pattern(也叫做 职责链模式),意思是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
在责任链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作责任链模式。
责任链模式有两种常用的实现。一种是使用链表来存储处理器,另一种是使用数组来存储处理器,后面一种实现方式更加简单。
责任链模式的应用场景: 框架中常用的过滤器、拦截器是如何实现的?
责任链模式常用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不需要修改框架源码的情况下,添加新的过滤拦截功能。这也体现了之前讲到的对扩展开放、对修改关闭的设计原则。Java中的 Servlet Filter 翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。
下面通过PHP的源代码演示责任链模式的使用,设定场景是对用户发表的评论内容进行非法内容校验拦截和关键词过滤替换。代码如下:
<?php
/**
* 责任链模式: 将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
*/
/**
* 词汇过滤链条
*/
abstract class FilterChain {
protected $next;
public function setNext($next) {
$this->next = $next;
}
abstract public function filter($message);
}
/**
* 链条第一步:非法内容拦截
*/
class FilterStrict extends FilterChain {
public function filter($message) {
try {
foreach (['黄X', '赌X', '毒X'] as $v) {
if (strpos($message, $v) !== false) {
throw new \Exception('信息包含敏感词汇,已被禁止发布!');
}
}
if ($this->next) {
return $this->next->filter($message);
} else {
return $message;
}
} catch (\Exception $e) {
return $e->getMessage();
}
}
}
/**
* 链条第二步:特殊关键词过滤替换
*/
class FilterWarning extends FilterChain {
public function filter($message) {
$message = str_replace(['打架', '斗殴', '暴力'], '*', $message);
if ($this->next) {
return $this->next->filter($message);
} else {
return $message;
}
}
}
/**
* 链条第三步: 检测如果包含手机号,则中间4位替换为*号
*/
class FilterMobile extends FilterChain {
public function filter($message) {
$message = preg_replace("/(1[3|5|7|8]\d)\d{4}(\d{4})/i", "$1****$2", $message);
if ($this->next) {
return $this->next->filter($message);
} else {
return $message;
}
}
}
$f1 = new FilterStrict();
$f2 = new FilterWarning();
$f3 = new FilterMobile();
$f1->setNext($f2);
$f2->setNext($f3);
//测试正常评论内容过滤关键词
//输出: 这是一条正常的评论,需要替换掉*和*这种词,然后给手机号加上星:133****3333,然后才可以对外展示
$comment_content1 = "这是一条正常的评论,需要替换掉打架和斗殴这种词,然后给手机号加上星:13333333333,然后才可以对外展示";
echo $f1->filter($comment_content1) . PHP_EOL;
//测试非法评论内容
//输出: 信息包含敏感词汇,已被禁止发布!
$comment_content2 = "这是一条非法的评论,因为包含了毒X,直接被拦截了";
echo $f1->filter($comment_content2) . PHP_EOL;
上面的代码看上去是不是很清晰呢,所以遇到有过滤器或者需要多次处理数据之类的场景都可以考虑使用责任链模式。
源代码:https://gitee.com/rxbook/php_design_pattern/blob/master/code07_ChainOfResponsibility.php