“单一职责原则”要求一个类仅负责的一个不可分业务逻辑,但这并不意味着能够实现这部分业务逻辑的只能有一个类,业务逻辑可能是会因运行时数据而选择不同类。比如在日常工作中,请假审批可能受请假天数、请假类型等因素影响,而须由不同领导来负责审批。再比如在银行取钱时,取钱业务审批申请可能会受到你所取钱总数、存储类型等因素影响,而须由不同经理或职员来负责办理。这一类事情其实在代码世界中更加常见,比如参数校验可能受到请求类型、请求数据的不同而由不同类(订单接口包括用户风控校验、限购校验、门店合法性、商品合法性等)来负责。这些案例都是同一套业务逻辑会由不同类型实现,那么如何更好的来完成功能执行过程呢?难道要每一个类全部都手动调一遍?之后增加其他类时还要修改大量的逻辑,非常不方便。这里的优化方案就是本文要讲述的责任链模式。
责任链模式的一般定义为:
使得每个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它位置。
从定义中看,责任链模式就是为了解决请求和多个需处理请求对象之间的耦合关系的,请求方不需要关心有哪些处理请求的对象,只需要感知这条“链”的存在即可。因此,责任链模式将多个对象处理请求的过程封装了起来,并且与请求方之间解耦。
后一句话指明了责任链中的对象负责的业务逻辑属于同一类(如请假审批),但不同对象责任存在差异(如主管审批与HR审批),在请求的过程中,只要在链上找到一个可以处理请求的对象即可。但是在日常开发中我们可能看到更多应用场景是所有对象都会处理请求(如用户风控校验、限购校验),不同对象之间处理请求并不会互相影响。后者在一些源码中应用于Filter、Handler等。
概念总结:
本章节给出一个关于“在线客服系统”的案例来说明下责任链模式。在线客服系统时,用户可以通过该系统向客服提出问题或请求帮助,其中由于公司业务扩展面会非常大,不同客服之间负责的方向存在很大的不提供。当用户提交问题,全部会由公司前台客服部门来负责接受请求。客服会在根据用户问题的难度和类型来判断是否自己是否可解决,否则应将问题抛给下一级客服部门,如业务部。依次类推,每一级客服都在这条责任链上,负责不同的任务,用户问题将会在这条责任链上流转,最终会流转到可以解决问题的部门客服。在上述案例中,设计到的对象可能有:
public abstract class CustomerService {
private CustomerService next;
public void setNext(CustomerService next) {
this.next = next;
}
public final void handleQuestion(Problem problem) {
if (canHandle(problem)) {
// 处理问题的逻辑
System.out.println("您的问题我来解决~");
doHandle(problem);
return;
}
if(next == null) {
throw new RuntimeException("无法处理相关问题");
}
next.handleQuestion(problem); // 交给下一级客服处理
}
protected abstract boolean canHandle(Problem problem);
protected abstract void doHandle(Problem problem);
}
前台客服:
public class FrontDeskCustomerService extends CustomerService {
@Override
protected boolean canHandle(Problem problem) {
// 判断是否能处理问题的逻辑
// 返回true表示能处理,返回false表示不能处理
return false;
}
@Override
protected void doHandle(Problem problem) {
// 问题处理过程中...
}
}
业务客服:
public class BusinessCustomerService extends CustomerService {
@Override
protected boolean canHandle(Problem problem) {
// 判断是否能处理问题的逻辑
// 返回true表示能处理,返回false表示不能处理
return false;
}
@Override
protected void doHandle(Problem problem) {
// 问题处理过程中...
}
}
客户端调用责任链:
public class Client {
public static void main(String[] args) {
// 创建客服对象
CustomerService frontDesk = new FrontDeskCustomerService();
CustomerService business = new BusinessCustomerService();
CustomerService technical = new TechnicalCustomerService();
CustomerService afterSales = new AfterSalesCustomerService();
// 构建责任链
frontDesk.setNext(business);
business.setNext(technical);
technical.setNext(afterSales);
// 创建问题对象
Problem problem = new Problem();
// 处理问题
frontDesk.handleQuestion(problem);
}
}
在上述代码中,四种客服类通过继承抽象类CustomerService实现了责任链模式,Client客户端可通过FrontDeskCustomerService#handleQuestion方法直接获取问题的解决办法,而无需感知具体内部是哪个客服类回复的问题。这里注意CustomerService我们也使用了模版方法模式,所有的客服类都需重写canHandle()、doHandle()两个基本方法,责任链的判断、执行、转发流程都封装在handleQuestion()这个模版方法内部。基于这样的责任链设计,即使后续新增其他客服类,也只需要继承CustomerService并重写两个基本方法即可。
很明显,这种责任链模式的设计是不符合依赖倒置原则的,Client与客服模块没有通过抽象层产生依赖而是直接依赖了首个具体实现类。不符合依赖倒置原则,肯定也不符合开闭原则了,因为当FrontDeskCustomerService发生改变时,需要检查所有依赖其的客户端,影响巨大。
责任链模式的优点:
责任链模式的缺点:
注:额外阅读材料