跳至主要內容

行为型职责链模式

gavin-james设计模式设计模式大约 8 分钟

职责链模式基本介绍

在现实生活中,一个事件需要经过多个对象处理是很常见的场景。例如,采购审批流程、请假流程等。公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。

在计算机软硬件中也有相关例子,如总线网中数据报传送,每台计算机根据目标地址是否同自己的地址相同来决定是否接收;还有异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSP 和 Servlet 的 Filter 等,所有这些都可以考虑使用职责链模式来实现。

职责链模式(Chain of Responsibility Pattern)又叫 责任链模式,为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。这种模式对请求的发送者和接收者进行解耦。

在职责链模式中,通常每个接收者都包含对另一个接收者的引用。客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递,如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。所以职责链将请求的发送者和请求的处理者解耦了。

这种类型的设计模式属于行为型模式。

主要优点

  • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息
  • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则
  • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任
  • 职责链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if-else 语句
  • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则

主要缺点

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用

职责链模式的结构与实现

职责链模式主要包含以下角色:

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程

责任链模式的本质是 解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

其结构图如下图所示:

image-20220326184616770
image-20220326184616770

客户端可按下图所示设置职责链:

image-20220326184654465
image-20220326184654465

代码实现

// 抽象处理者角色
abstract class Handler {
    private Handler next;
    public void setNext(Handler next) {
        this.next = next;
    }
    public Handler getNext() {
        return next;
    }
    // 处理请求的方法
    public abstract void handleRequest(String request);
}
// 具体处理者角色 1
class ConcreteHandler1 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("one")) {
            System.out.println("具体处理者1负责处理该请求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("没有人处理该请求!");
            }
        }
    }
}
// 具体处理者角色 2
class ConcreteHandler2 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("two")) {
            System.out.println("具体处理者2负责处理该请求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("没有人处理该请求!");
            }
        }
    }
}
public class ChainOfResponsibilityPattern {
    public static void main(String[] args) {
        //组装责任链
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        handler1.setNext(handler2);
        //提交请求
        handler1.handleRequest("two");
    }
}

学校 OA 系统的采购审批项目

采购员采购教学器材

  • 如果金额小于等于 5000,由教学主任审批(0 <= x <= 5000
  • 如果金额小于等于 10000,由院长审批(5000 < x <= 10000
  • 如果金额小于等于 30000,由副校长审批(10000 < x <= 30000
  • 如果金额超过 30000 以上,有校长审批(30000 < x

传统方案题分析

  • 传统方式是:接收到一个采购请求后,根据采购金额来调用对应的 Approver(审批人)完成审批
  • 传统方式的问题分析:客户端这里会使用到分支判断(比如 Switch)来对不同的采购请求处理,这样就存在如下问题:
    • 如果各个级别的人员审批金额发生变化,在客户端的也需要变化
    • 客户端必须明确的知道有多少个审批级别和访问
  • 这样对一个采购请求进行处理和 Approver(审批人)就存在强耦合关系,不利于代码的扩展和维护

解决方案:职责链模式

职责链模式解决采购审批项目

思路分析和类图:

image-20220326183929550
image-20220326183929550

代码实现:

职责链类

public abstract class Approver {
    Approver approver;  // 下一个处理者
    String name; // 名字

    public Approver(String name) {
        this.name = name;
    }
    // 下一个处理者
    public void setApprover(Approver approver) {
        this.approver = approver;
    }
    // 处理审批请求的方法,得到一个请求,处理是子类完成,因此该方法做成抽象
    public abstract void processRequest(PurchaseRequest purchaseRequest);

}
// 学校管理者(权力最大)
public class SchoolMasterApprover extends Approver {

    public SchoolMasterApprover(String name) {
        super(name);
    }
    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if(purchaseRequest.getPrice() > 30000) {
            System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
        }else {
            approver.processRequest(purchaseRequest);
        }
    }
}
// 学校副管理者(权力第二)
public class ViceSchoolMasterApprover extends Approver {

    public ViceSchoolMasterApprover(String name) {
        super(name);
    }
    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if(purchaseRequest.getPrice() < 10000 && purchaseRequest.getPrice() <= 30000) {
            System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
        }else {
            approver.processRequest(purchaseRequest);
        }
    }
}
// 院系管理者(权力第三)
public class CollegeApprover extends Approver {

    public CollegeApprover(String name) {
        super(name);
    }
    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if(purchaseRequest.getPrice() < 5000 && purchaseRequest.getPrice() <= 10000) {
            System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
        }else {
            approver.processRequest(purchaseRequest);
        }
    }
}
// 班级管理者(权力第四)
public class DepartmentApprover extends Approver {

    public DepartmentApprover(String name) {
        super(name);
    }
    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if(purchaseRequest.getPrice() <= 5000) {
            System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
        }else {
            approver.processRequest(purchaseRequest);
        }
    }
}

请求类

// 请求类
public class PurchaseRequest {
    private int type = 0; // 请求类型
    private float price = 0.0f; // 请求金额
    private int id = 0;
    // 构造器
    public PurchaseRequest(int type, float price, int id) {
        this.type = type;
        this.price = price;
        this.id = id;
    }
    public int getType() {
        return type;
    }
    public float getPrice() {
        return price;
    }
    public int getId() {
        return id;
    }
}

测试类

public class Client {
    public static void main(String[] args) {
        // 创建一个请求
        PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);

        // 创建相关的审批人
        DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
        CollegeApprover collegeApprover = new CollegeApprover("李院长");
        ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
        SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");

        // 需要将各个审批级别的下一个设置好 (处理人构成环形)
        departmentApprover.setApprover(collegeApprover);
        collegeApprover.setApprover(viceSchoolMasterApprover);
        viceSchoolMasterApprover.setApprover(schoolMasterApprover);
        schoolMasterApprover.setApprover(departmentApprover);

        departmentApprover.processRequest(purchaseRequest);
    }
}

SpringMVC 的 HandlerExecutionChain 类就使用到职责链模式。

职责链模式的注意事项和细节

  • 职责链模式将请求和处理分开,实现解耦,提高系统的灵活性
  • 职责链模式简化了对象,使对象不需要知道链的结构
  • 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext() 方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
  • 调试不方便,采用了类似递归的方式,调试时逻辑可能比较复杂

职责链模式的应用场景

责任链模式通常在以下几种情况使用:

  • 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定
  • 可动态指定一组对象处理请求,或添加新的处理者
  • 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求