今天给大家介绍责任链模式(Chain of Responsibility Pattern),责任链模式是属于设计模式中的行为型模式。巧用设计模式可以让我们的代码解耦,提高可拓展性和维护性,但一定不要为了使用而使用。
一、定义
- 原文:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
- 维基百科定义:In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.
在面向对象的设计中,责任链模式是一种由命令对象的源和一系列处理对象组成的设计模式。每个处理对象都包含定义其可以处理的命令对象类型的逻辑。其余的将传递到链中的下一个处理对象。
- 简要总结:责任链模式是将链中的每一个节点看做一个对象,且内部维护下一个节点对象的引用,形成一个单链表,当收到一个请求时,会沿着链的路径一次传递给每一个节点对象,当然每个节点对象可以选择继续传递或者提前退出,直到最后一个节点对象。
二、应用
1. 应用场景
责任链模式的使用也是比较广泛的,比如我们OA系统的审批流,或者某一个特殊的业务需要经过几个比较复杂的流程处理等,比如一个添加业务,假设我们先要进行数据校验,然后进行入库操作,最后要通知其他系统做特殊处理,就可以做成一个责任链模式,代码整体耦合性降低,易于维护。
2. 角色
-
Handler(抽象处理者)
定义请求处理方法,以及维护下一个处理者的引用
-
ConcreteHandler(具体处理者)
处理请求的具体角色
3. 类图
责任链模式的类图比较简单,所有处理节点都继承同一个父类,这个父类可以是抽象类或者具体实现类,类中维护下一个处理节点的引用以及节点的处理请求方法(这个方法可以是抽象方法,也可以是具体实现,当然还可以利用钩子方法实现对子类方法的调用),调用者不关注具体节点的实现,只调用节点的处理方法即可。
4. 举例说明
假设现在有一个请假流程,小于三天组长审批即可,大于3天同时需要CTO审批,大于10天同时需要CEO审批。组长和CTO以及CEO都有权利审批通过或者驳回,如果审批通过,根据请假天数判断是否需要流转到下一节点,如果驳回请求,则直接返回。
首先看下整体类图:
首先创建一个父类处理器,
public class RequestHandler {
private RequestHandler next; // 下一个节点的引用
public RequestHandler(RequestHandler next) {
this.next = next;
}
public void handleRequest(Request request) {
if (next != null) { // 如果下一个节点不为空,则继续调用下一个节点的处理方法
next.handleRequest(request);
}
}
}
创建三个节点处理对象,分别是组长处理、CTO处理、CEO处理:
public class LeaderHandler extends RequestHandler {
public LeaderHandler(RequestHandler next) {
super(next);
}
@Override
public void handleRequest(Request request) {
if (request.getEmployeeName().equals("张三")) {
System.out.println("组长驳回【 " + request.getEmployeeName() + " 】请假");
request.setReject();
return;
}
System.out.println("组长通过【 " + request.getEmployeeName() + " 】请假");
if (request.getDays() > 3) {
super.handleRequest(request);
} else {
request.setPass();
}
}
}
public class CTOHandler extends RequestHandler {
public CTOHandler(RequestHandler next) {
super(next);
}
@Override
public void handleRequest(Request request) {
if (request.getEmployeeName().equals("李四")) {
System.out.println("CTO驳回【 " + request.getEmployeeName() + " 】请假");
request.setReject();
return;
}
System.out.println("CTO通过【 " + request.getEmployeeName() + " 】请假");
if (request.getDays() > 10) {
super.handleRequest(request);
} else {
request.setPass();
}
}
}
public class CEOHandler extends RequestHandler {
public CEOHandler(RequestHandler next) {
super(next);
}
@Override
public void handleRequest(Request request) {
if (request.getEmployeeName().equals("王五")) {
System.out.println("CEO驳回【 " + request.getEmployeeName() + " 】请假");
request.setReject();
return;
}
System.out.println("CEO通过【 " + request.getEmployeeName() + " 】请假");
request.setPass();
}
}
然后创建一个请假处理对象:
public class Request {
private String employeeName; // 员工姓名
private int days; // 请假天数
private boolean handleResult; // 请假是否通过
public Request(String employeeName, int days) {
this.employeeName = employeeName;
this.days = days;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public int getDays() {
return days;
}
public void setDays(int days) {
this.days = days;
}
public void setPass() {
this.handleResult = true;
}
public void setReject() {
this.handleResult = false;
}
public boolean getHandleResult() {
return this.handleResult;
}
}
建立一个测试类:
public class ChainTest {
public static void main(String[] args) {
RequestHandler chain = new LeaderHandler(new CTOHandler(new CEOHandler(null)));
Request zhangsan = new Request("张三", 3);
chain.handleRequest(zhangsan);
System.out.println("张三请假结果:" + zhangsan.getHandleResult());
System.out.println("-------------------");
Request lisi = new Request("李四", 5);
chain.handleRequest(lisi);
System.out.println("李四请假结果:" + lisi.getHandleResult());
System.out.println("-------------------");
Request wangwu = new Request("王五", 15);
chain.handleRequest(wangwu);
System.out.println("王五请假结果:" + wangwu.getHandleResult());
System.out.println("-------------------");
Request oldZhao = new Request("老赵", 15);
chain.handleRequest(oldZhao);
System.out.println("老赵请假结果:" + oldZhao.getHandleResult());
}
}
打印结果如下:
组长驳回【 张三 】请假
张三请假结果:false
-------------------
组长通过【 李四 】请假
CTO驳回【 李四 】请假
李四请假结果:false
-------------------
组长通过【 王五 】请假
CTO通过【 王五 】请假
CEO驳回【 王五 】请假
王五请假结果:false
-------------------
组长通过【 老赵 】请假
CTO通过【 老赵 】请假
CEO通过【 老赵 】请假
老赵请假结果:true
我们可以看到输入不同的参数,所走的业务流程不同,有的只经过一个处理器,有的会走完所有的处理器。
三、和建造者模式的结合使用
上面的示例中,在父类维护next节点的引用我们是通过构造器来维护的,所以我们在初始化处理链的时候是通过下面代码实现的
RequestHandler chain = new LeaderHandler(new CTOHandler(new CEOHandler(null)));
可以看到代码整体显得比较臃肿,如果我们有六七个或者更多处理节点的时候,易读性会非常差,
而大多数情况下一般都是通过父类提供一个setNextHandler(RequestHandler handler)来维护下一个节点,如果用此方法来初始化处理链的话,代码如下:
LeaderHandler leader = new LeaderHandler();
CTOHandler cto = new CTOHandler();
CEOHandler ceo = new CEOHandler();
leader.setNextHandler(cto);
cto.setNextHandler(ceo);
我们可以看到需要不断的给当前节点设置下一个节点,易读性和维护性也是比较差的,至此我们想到了利用建造者模式和责任链模式的结合使用,来让此处的设计变的解耦又好维护。
我们在RequestHandler类中维护一个内部类,内部类中使用单向链表来维护处理节点的顺序,再通过addHandler方法添加所有处理链,在调用的时候提供一个build方法获取头结点,然后通过头节点的next属性获取后续所有的节点,这样只要我们在初始化处理链的时候按顺序添加处理结点,就不用在设置next结点,代码的可读性也提升了很多。
public class RequestHandler {
private RequestHandler next;
public void setNextHandler(RequestHandler next) {
this.next = next;
}
public void handleRequest(Request request) {
if (next != null) {
next.handleRequest(request);
}
}
public static class Builder {
private RequestHandler head;
private RequestHandler tail;
public Builder addHandler(RequestHandler requestHandler) {
if (head == null) {
this.head = this.tail = requestHandler;
} else {
this.tail.next = requestHandler;
this.tail = requestHandler;
}
return this;
}
public RequestHandler build() {
return this.head;
}
}
}
调用的时候通过如下代码即可:
public class ChainTest {
public static void main(String[] args) {
RequestHandler.Builder builder = new RequestHandler.Builder();
builder.addHandler(new LeaderHandler())
.addHandler(new CTOHandler())
.addHandler(new CEOHandler());
RequestHandler handler = builder.build();
Request zhangsan = new Request("张三", 3);
handler.handleRequest(zhangsan);
System.out.println("张三请假结果:" + zhangsan.getHandleResult());
System.out.println("-------------------");
Request lisi = new Request("李四", 5);
handler.handleRequest(lisi);
System.out.println("李四请假结果:" + lisi.getHandleResult());
System.out.println("-------------------");
Request wangwu = new Request("王五", 15);
handler.handleRequest(wangwu);
System.out.println("王五请假结果:" + wangwu.getHandleResult());
System.out.println("-------------------");
Request oldZhao = new Request("老赵", 15);
handler.handleRequest(oldZhao);
System.out.println("老赵请假结果:" + oldZhao.getHandleResult());
}
}
四、在源码中的体现
public void log(LogRecord record) {
if (!isLoggable(record.getLevel())) {
return;
}
Filter theFilter = config.filter;
if (theFilter != null && !theFilter.isLoggable(record)) {
return;
}
// Post the LogRecord to all our Handlers, and then to
// our parents' handlers, all the way up the tree.
Logger logger = this;
while (logger != null) {
final Handler[] loggerHandlers = isSystemLogger
? logger.accessCheckedHandlers()
: logger.getHandlers();
for (Handler handler : loggerHandlers) {
handler.publish(record);
}
final boolean useParentHdls = isSystemLogger
? logger.config.useParentHandlers
: logger.getUseParentHandlers();
if (!useParentHdls) {
break;
}
logger = isSystemLogger ? logger.parent : logger.getParent();
}
}
此处是把所有的handler都维护在一个数组中,然后通过循环handler调用next的处理结点
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
Assert.state(this.request == null, "This FilterChain has already been called!");
if (this.iterator == null) {
this.iterator = this.filters.iterator();
}
if (this.iterator.hasNext()) {
Filter nextFilter = (Filter)this.iterator.next();
nextFilter.doFilter(request, response, this);
}
this.request = request;
this.response = response;
}
这里是将所有的Filter放到list中,然后通过调用doFilter()方法时循环迭代list,list中的Filter也会顺序执行。
五、优缺点
优点:
- 降低代码的耦合度,将请求与处理解耦
- 节点对象只需关注自己要处理的业务或者特定的请求即可,对于不想处理的可以直接传递给下一个节点
- 链路结构灵活,可以通过改变链路结构动态的新增或者删减责任
- 可以实时新增链路节点,符合开闭原则
缺点:
- 责任链太长或者处理时间过长,会影响整体性能
- 整体调用有点类似递归,排查问题较为费力
- 代码拆分的小类会比较多