职责链模式
发表于更新于
设计模式职责链模式
Rif设计模式笔记:职责链模式 (Chain of Responsibility Pattern)
一、一句话概括
为请求创建了一个接收者对象的链,并让请求在链上传递,直到链上的某个接收者处理它为止。这避免了将请求的发送者和接收者耦合在一起。
二、为什么需要它 (Why - The Pain Point)
想象一个公司的费用报销审批流程:
- 金额小于等于 500 元,项目经理(Team Leader) 可以直接审批。
- 金额大于 500 元但小于等于 5000 元,需要部门主管(Department Head) 审批。
- 金额大于 5000 元,需要 CEO 审批。
如果你用传统的 if-else 或 switch 语句来写这个逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class ExpenseReport { double amount; }
class ApprovalSystem { public void approve(ExpenseReport report) { if (report.getAmount() <= 500) { System.out.println("Approved by Team Leader."); } else if (report.getAmount() <= 5000) { System.out.println("Approved by Department Head."); } else { System.out.println("Approved by CEO."); } } }
|
痛点:僵化且难以维护
- 违反开闭原则:如果公司的审批流程发生变化,比如在部门主管和 CEO 之间增加一个“总监(Director)”级别,你就必须修改
ApprovalSystem 类的 approve 方法,增加一个新的 else if 分支。
- 高耦合:
ApprovalSystem 这个类耦合了所有的审批逻辑,变得非常臃肿。请求的提交者(比如员工)也直接依赖于这个集中的处理系统。
- 职责不单一:
ApprovalSystem 承担了所有级别的审批职责。
- 灵活性差:无法在运行时动态地调整审批流程或顺序。
核心问题:如何让多个对象都有机会处理一个请求,同时又避免请求的发送者和接收者之间产生紧密的、硬编码的联系?
三、它是什么 (What - The Solution)
职责链模式将这些“有能力处理请求的对象”连接成一条链。当一个请求到来时,它会沿着这条链进行传递,链上的每个对象都有机会处理它。
核心思想和角色:
- **处理器接口/抽象类 (Handler)**:
- 定义了处理请求的接口,通常包含一个
handleRequest(Request request) 方法。
- 关键部分:它还包含一个指向下一个处理器(
successor)的引用。
- 它提供了一个默认的实现:如果自己不能处理该请求,就将请求传递给下一个处理器,即
successor.handleRequest(request)。
- **具体处理器 (Concrete Handler)**:
- 实现了处理器接口。
- 它负责处理它感兴趣的请求。
- 在
handleRequest 方法中,它会先判断自己是否能处理这个请求。
- 如果能,就处理它,然后可以选择是否继续向下传递。
- 如果不能,就直接将请求转发给它的后继者。
- **客户端 (Client)**:
- 客户端负责创建和组装这条职责链。
- 它只需要将请求发送给链的第一个处理器,然后就不再关心请求由谁、以及如何被处理。
工作流程:
- 客户端创建一个报销请求。
- 客户端将请求发送给审批链的第一个节点(项目经理)。
- 项目经理检查金额:
- 如果金额 <= 500,处理它,流程结束。
- 如果金额 > 500,他处理不了,于是将请求传递给他的后继者(部门主管)。
- 部门主管检查金额:
- 如果金额 <= 5000,处理它,流程结束。
- 如果金额 > 5000,他处理不了,于是将请求传递给他的后继者(CEO)。
- CEO 拥有最高权限,处理请求,流程结束。
四、怎么做 (How - The Blueprint)
基本步骤(以费用审批为例):
- **创建请求类
ExpenseReport**,包含金额等信息。
- **创建处理器抽象类
Approver**。
- 包含一个
Approver successor 字段。
- 定义一个抽象方法
handleRequest(ExpenseReport report)。
- 创建具体处理器类,如
TeamLeader, DepartmentHead, CEO,它们都继承自 Approver。
- 在各自的
handleRequest 方法中,实现自己的处理逻辑和转发逻辑。
- 客户端创建处理器实例,并通过
setSuccessor() 方法将它们链接起来,形成一条完整的审批链。
- 客户端将报销请求发送给链的头部。
Java 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| class ExpenseReport { private final String title; private final double amount;
public ExpenseReport(String title, double amount) { this.title = title; this.amount = amount; } public double getAmount() { return amount; } public String getTitle() { return title; } }
abstract class Approver { protected Approver successor;
public void setSuccessor(Approver successor) { this.successor = successor; }
public abstract void handleRequest(ExpenseReport report); }
class TeamLeader extends Approver { @Override public void handleRequest(ExpenseReport report) { if (report.getAmount() <= 500) { System.out.println("Team Leader approved: " + report.getTitle()); } else if (successor != null) { System.out.println("Team Leader cannot approve. Passing to successor."); successor.handleRequest(report); } } }
class DepartmentHead extends Approver { @Override public void handleRequest(ExpenseReport report) { if (report.getAmount() <= 5000) { System.out.println("Department Head approved: " + report.getTitle()); } else if (successor != null) { System.out.println("Department Head cannot approve. Passing to successor."); successor.handleRequest(report); } } }
class CEO extends Approver { @Override public void handleRequest(ExpenseReport report) { System.out.println("CEO approved: " + report.getTitle()); } }
public class ChainOfResponsibilityDemo { public static void main(String[] args) { Approver leader = new TeamLeader(); Approver head = new DepartmentHead(); Approver ceo = new CEO();
leader.setSuccessor(head); head.setSuccessor(ceo);
ExpenseReport report1 = new ExpenseReport("Team building", 300); System.out.println("--- Processing report 1 (amount: 300) ---"); leader.handleRequest(report1);
System.out.println();
ExpenseReport report2 = new ExpenseReport("Purchase new server", 4500); System.out.println("--- Processing report 2 (amount: 4500) ---"); leader.handleRequest(report2);
System.out.println();
ExpenseReport report3 = new ExpenseReport("Company acquisition", 100000); System.out.println("--- Processing report 3 (amount: 100000) ---"); leader.handleRequest(report3); } }
|
五、UML 类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @startuml skinparam classAttributeIconSize 0 hide empty members
abstract class Approver { # Approver successor + void setSuccessor(Approver s) + {abstract} void handleRequest(ExpenseReport r) }
class TeamLeader extends Approver { + void handleRequest(ExpenseReport r) } class DepartmentHead extends Approver { + void handleRequest(ExpenseReport r) } class CEO extends Approver { + void handleRequest(ExpenseReport r) }
class ExpenseReport {}
' Relationship: The chain Approver o-- "1" Approver : successor >
class ChainOfResponsibilityDemo {} ChainOfResponsibilityDemo ..> Approver : uses ChainOfResponsibilityDemo ..> ExpenseReport : creates @enduml
|
图示解读:
TeamLeader, DepartmentHead, CEO 都继承自 Approver。
Approver 内部有一个指向自己的引用 successor,这构成了链式结构。
- 客户端只与链的头部(一个
Approver)交互。
六、优缺点分析
优点 (Pros):
- 降低耦合度:请求的发送者完全不知道是哪个接收者处理了请求,也不知道链的结构。接收者也无需知道发送者的信息。
- 增强了灵活性:可以随时增加、删除或重新排序链上的处理器,而无需修改客户端代码或其他处理器的代码,符合开闭原则。
- 职责分配更清晰:每个处理器只关心自己能处理的请求,职责明确。
缺点 (Cons):
- 请求不保证被处理:如果链的构造不当,或者没有任何处理器能够处理该请求,那么请求可能会传递到链的末尾而未被处理。
- 调试困难:由于请求在链中传递,如果链条很长,追踪请求的处理过程可能会变得复杂。
- 可能影响性能:请求需要遍历链上的多个节点,可能会有一定的性能开销。
七、在 Java 中的应用
- **Java Servlet 过滤器 (Filter Chain)**:这是职责链模式最经典、最完美的工业级应用。
- 当一个 HTTP 请求到达 Web 服务器时,它会经过一个由多个
Filter 组成的链(FilterChain)。
- 每个
Filter(具体处理器)都可以对请求进行检查或修改(如编码转换、权限验证、日志记录)。
- 如果
Filter 决定继续处理,它会调用 chain.doFilter(request, response) 将请求传递给链上的下一个 Filter 或最终的 Servlet。
- 你可以通过
web.xml 或注解来配置这个过滤器链,非常灵活。
- Java 异常处理机制 (try-catch): 在某种程度上,Java 的
try-catch 块也体现了职责链的思想。当一个异常被抛出时,它会沿着调用栈向上“传递”,直到找到第一个能够捕获并处理该类型异常的 catch 块。
- Log4j / Logback 的 Appender: 日志框架中的
Appender 也可以形成一个链,一个日志事件可以被多个 Appender 处理(比如同时输出到控制台和文件)。