系列定位:软考软件设计师 / 系统架构设计师 --- 行为型模式专题第 10 讲
考察分值:上午题 1-2 分,下午题常作为代码填空或类图识别出现
难度等级:⭐⭐⭐☆☆(结构清晰,Handler 链式引用与请求转发逻辑是填空重点)
一、考纲定位与模式定义
1.1 考纲要求
责任链模式在软考中属于 行为型模式 的基础内容。考察形式包括:
-
上午选择题:判断场景描述所属模式;识别责任链模式与观察者模式、装饰模式的区别;判断 Handler、ConcreteHandler、Client 三个角色;识别 "请求沿链传递直到被处理" 的设计意图
-
下午设计题 :补全
Handler抽象类中setSuccessor()和handleRequest()的代码;补全ConcreteHandler中判断能否处理请求以及转发给后继者的逻辑;识别类图中 Handler 之间的链式引用关系(通常用next或successor字段)
1.2 模式定义
责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
核心意图 :当一个系统需要 处理某种请求 ,且存在 多个对象 都有能力处理该请求,但具体由哪个对象处理取决于请求的类型或条件时,如果客户端直接决定调用哪个处理对象,会导致:
-
客户端与处理对象紧耦合,需要知道所有处理对象的细节
-
新增处理对象时需要修改客户端代码
-
处理逻辑散落在客户端,难以维护
责任链模式将这些处理对象 连成一条链 ,客户端只需要把请求交给链头的第一个对象。每个对象判断自己能否处理:能处理就直接处理,不能处理就把请求 转发给链上的下一个对象。请求在链上传递,直到被某个对象处理,或者传到链尾也没人处理。
通俗理解:
你去医院看病,先挂门诊号。门诊医生看了说 "这病我看不了,你去找专科主任";专科主任看了说 "这病太复杂,你去找专家教授";专家教授看了说 "这我能治",然后给你开了药。你(客户端)不需要一开始就知道自己该找谁,只需要把病历交给门诊医生(链头),病历会沿着医生级别这条链自动传递,直到有人能治。
如果没有责任链,你得自己研究每个医生的专长,然后判断该挂哪个号------这就是客户端与处理对象的紧耦合。
二、UML 类图与角色划分
┌─────────────────────────────────────────────────────────┐
│ Client │
│ + main() │
│ ├── Handler h1 = new ConcreteHandlerA() │
│ ├── Handler h2 = new ConcreteHandlerB() │
│ ├── Handler h3 = new ConcreteHandlerC() │
│ ├── h1.setSuccessor(h2) // 组装责任链 │
│ ├── h2.setSuccessor(h3) │
│ └── h1.handleRequest(request) // 请求交给链头 │
│ // 请求在链上传递,直到被处理 │
└───────────────────────┬─────────────────────────────────┘
│ uses
▼
┌─────────────────────────────────────────────────────────┐
│ Handler │
│ ── 抽象处理者:定义处理请求接口 + 持有后继者 ── │
│ - successor: Handler ◄── 链式引用,后继者 │
│ + setSuccessor(Handler h) { │
│ this.successor = h; │
│ } │
│ + handleRequest(Request req) { │
│ // 模板方法:子类覆盖判断逻辑 │
│ if (canHandle(req)) { │
│ process(req); │
│ } else if (successor != null) { │
│ successor.handleRequest(req); // 转发给后继者 │
│ } else { │
│ System.out.println("无人处理该请求"); │
│ } │
│ } │
│ # canHandle(Request): boolean ◄── 抽象/钩子方法 │
│ # process(Request) ◄── 抽象/钩子方法 │
└───────────────────────┬─────────────────────────────────┘
▲ │
│ extends │
│ │
┌────────┴────────┐ │
▼ ▼ │
┌─────────────────┐ ┌─────────────────────────────────────┐
│ ConcreteHandlerA│ │ ConcreteHandlerB │
│ (请假≤3天) │ │ (3<请假≤7天) │
│ + canHandle(req)│ │ + canHandle(req) { │
│ { days<=3 } │ │ return days > 3 && days <= 7 │
│ + process(req) │ │ } │
│ { 主管审批 } │ │ + process(req) { 经理审批 } │
└─────────────────┘ └─────────────────────────────────────┘
▲
│ extends
▼
┌─────────────────────────────────────────────────────────┐
│ ConcreteHandlerC │
│ (请假>7天) │
│ + canHandle(req) { return days > 7 } │
│ + process(req) { 总经理审批 } │
└─────────────────────────────────────────────────────────┘
| 角色 | 职责 | 软考填空关键词 |
|---|---|---|
| Handler(抽象处理者) | 定义处理请求的接口,通常包含 setSuccessor() 方法设置后继者,以及 handleRequest() 方法处理请求。handleRequest() 通常定义了请求转发的模板逻辑 |
abstract class Handler + protected Handler successor + setSuccessor() + handleRequest() |
| ConcreteHandler(具体处理者) | 实现 handleRequest() 方法,判断自己能否处理请求:能处理则执行处理逻辑,不能处理则调用 successor.handleRequest() 转发给后继者 |
extends Handler + handleRequest() 中 if (条件) { 处理 } else { successor.handleRequest() } |
| Client(客户端) | 创建多个 ConcreteHandler 对象,通过 setSuccessor() 组装成责任链,然后向链头提交请求 |
new HandlerA() + a.setSuccessor(b) + a.handleRequest(req) |
类图识别要点 :软考类图中,责任链模式的核心特征是 Handler 类持有
successor(或next)字段指向另一个 Handler,形成链式结构,且handleRequest()方法中存在 "自己处理不了就转发给 successor" 的逻辑。如果类图里有多个 Handler 类连成链,且请求沿链传递 → 责任链模式。
三、场景一:请假审批流程(最经典的责任链模式)
业务背景:公司请假审批规则:请假 ≤3 天由主管审批,3~7 天由经理审批,>7 天由总经理审批。员工提交请假申请时,系统根据请假天数自动找到合适的审批人。要求新增审批级别时不需要修改员工提交申请的代码。
说明:这是责任链模式最经典的例子,也是软考下午题最爱考的 "多级审批" 场景。
3.1 代码实现
java
// 请假请求类
public class LeaveRequest {
private String employeeName;
private int leaveDays;
private String reason;
public LeaveRequest(String employeeName, int leaveDays, String reason) {
this.employeeName = employeeName;
this.leaveDays = leaveDays;
this.reason = reason;
}
public String getEmployeeName() { return employeeName; }
public int getLeaveDays() { return leaveDays; }
public String getReason() { return reason; }
}
// Handler:抽象审批者
public abstract class Approver {
protected Approver successor; // 后继者
protected String name; // 审批人姓名
public Approver(String name) {
this.name = name;
}
// 设置后继者
public void setSuccessor(Approver successor) {
this.successor = successor;
}
// 处理请求:模板方法
public abstract void handleRequest(LeaveRequest request);
}
// ConcreteHandler:主管(审批 ≤3 天)
public class Supervisor extends Approver {
public Supervisor(String name) {
super(name);
}
@Override
public void handleRequest(LeaveRequest request) {
if (request.getLeaveDays() <= 3) {
System.out.println("【主管】" + name + " 审批通过: " +
request.getEmployeeName() + " 请假 " + request.getLeaveDays() +
" 天,原因: " + request.getReason());
} else if (successor != null) {
System.out.println("【主管】" + name + " 无权审批,转交给上级...");
successor.handleRequest(request); // 转发给后继者
}
}
}
// ConcreteHandler:经理(审批 3~7 天)
public class Manager extends Approver {
public Manager(String name) {
super(name);
}
@Override
public void handleRequest(LeaveRequest request) {
if (request.getLeaveDays() > 3 && request.getLeaveDays() <= 7) {
System.out.println("【经理】" + name + " 审批通过: " +
request.getEmployeeName() + " 请假 " + request.getLeaveDays() +
" 天,原因: " + request.getReason());
} else if (successor != null) {
System.out.println("【经理】" + name + " 无权审批,转交给上级...");
successor.handleRequest(request);
}
}
}
// ConcreteHandler:总经理(审批 >7 天)
public class GeneralManager extends Approver {
public GeneralManager(String name) {
super(name);
}
@Override
public void handleRequest(LeaveRequest request) {
if (request.getLeaveDays() > 7) {
System.out.println("【总经理】" + name + " 审批通过: " +
request.getEmployeeName() + " 请假 " + request.getLeaveDays() +
" 天,原因: " + request.getReason());
} else {
System.out.println("【总经理】" + name + " 驳回申请: 请假天数异常");
}
}
}
// Client:客户端
public class Client {
public static void main(String[] args) {
// 创建处理者
Approver supervisor = new Supervisor("张主管");
Approver manager = new Manager("李经理");
Approver generalManager = new GeneralManager("王总");
// 组装责任链:主管 -> 经理 -> 总经理
supervisor.setSuccessor(manager);
manager.setSuccessor(generalManager);
// 创建请假请求
LeaveRequest req1 = new LeaveRequest("小明", 2, "感冒");
LeaveRequest req2 = new LeaveRequest("小红", 5, "家中有事");
LeaveRequest req3 = new LeaveRequest("小刚", 10, "出国旅游");
// 所有请求都交给链头(主管),由链自动传递
System.out.println("=== 请假2天 ===");
supervisor.handleRequest(req1);
System.out.println("=== 请假5天 ===");
supervisor.handleRequest(req2);
System.out.println("=== 请假10天 ===");
supervisor.handleRequest(req3);
}
}
关键体会:
-
Approver(Handler)抽象类定义了successor字段和handleRequest()模板方法。 -
Supervisor、Manager、GeneralManager(ConcreteHandler)各自判断能否处理请求:能处理就审批,不能处理就调用successor.handleRequest()转发。 -
客户端只需要把请求交给链头的
supervisor,不需要知道具体该由谁审批。请求在链上自动传递,直到被处理。 -
新增一个审批级别(如 "总监" 审批 7~15 天),只需在链中插入一个新的
ConcreteHandler,不需要修改 客户端代码。这符合 开闭原则。
四、场景二:日志过滤器链(实际开发场景)
业务背景:系统需要记录日志,根据日志级别(DEBUG、INFO、WARN、ERROR)由不同的处理器处理:DEBUG 日志输出到控制台,INFO 日志写入文件,ERROR 日志发送邮件告警。一条日志可能触发多个处理器(如 ERROR 日志既写文件又发邮件),也可能只触发一个。使用责任链模式将日志处理解耦。
说明:这是实际开发中责任链模式的典型应用(Java 的 java.util.logging 和 Log4j 均使用类似设计),也是软考下午题中 "多级处理" 场景的代表。
4.1 代码实现
java
// 日志请求类
public class LogMessage {
public static final int DEBUG = 1;
public static final int INFO = 2;
public static final int WARN = 3;
public static final int ERROR = 4;
private int level;
private String message;
public LogMessage(int level, String message) {
this.level = level;
this.message = message;
}
public int getLevel() { return level; }
public String getMessage() { return message; }
}
// Handler:抽象日志处理器
public abstract class Logger {
protected int level; // 当前处理器能处理的级别
protected Logger nextLogger; // 下一个处理器
public Logger(int level) {
this.level = level;
}
public void setNextLogger(Logger nextLogger) {
this.nextLogger = nextLogger;
}
// 处理日志:模板方法
public void logMessage(int level, String message) {
if (this.level <= level) { // 能处理该级别
write(new LogMessage(level, message));
}
if (nextLogger != null) { // 无论是否处理,都继续传递给下一个(与审批场景不同)
nextLogger.logMessage(level, message);
}
}
protected abstract void write(LogMessage msg);
}
// ConcreteHandler:控制台日志(DEBUG 及以上)
public class ConsoleLogger extends Logger {
public ConsoleLogger() {
super(LogMessage.DEBUG);
}
@Override
protected void write(LogMessage msg) {
System.out.println("【控制台】" + msg.getMessage());
}
}
// ConcreteHandler:文件日志(INFO 及以上)
public class FileLogger extends Logger {
public FileLogger() {
super(LogMessage.INFO);
}
@Override
protected void write(LogMessage msg) {
System.out.println("【文件日志】记录到文件: " + msg.getMessage());
}
}
// ConcreteHandler:邮件告警(ERROR 及以上)
public class EmailLogger extends Logger {
public EmailLogger() {
super(LogMessage.ERROR);
}
@Override
protected void write(LogMessage msg) {
System.out.println("【邮件告警】发送邮件: " + msg.getMessage());
}
}
// Client
public class Client {
public static void main(String[] args) {
// 创建处理器
Logger console = new ConsoleLogger();
Logger file = new FileLogger();
Logger email = new EmailLogger();
// 组装责任链:控制台 -> 文件 -> 邮件
console.setNextLogger(file);
file.setNextLogger(email);
// 测试不同级别日志
System.out.println("=== DEBUG 日志 ===");
console.logMessage(LogMessage.DEBUG, "调试信息");
System.out.println("=== INFO 日志 ===");
console.logMessage(LogMessage.INFO, "普通信息");
System.out.println("=== ERROR 日志 ===");
console.logMessage(LogMessage.ERROR, "系统错误");
}
}
关键体会:
-
日志场景与审批场景的区别:审批是 "一人处理就结束" (请求被处理即停止传递),日志是 "能处理就处理,然后继续传递"(请求被处理但不停止,继续传给下一个)。
-
ConsoleLogger处理 DEBUG 及以上级别,FileLogger处理 INFO 及以上,EmailLogger处理 ERROR 及以上。ERROR 日志会触发所有三个处理器。 -
这就是责任链的 两种变体:纯责任链(只有一个处理器处理,处理完即停)和 不纯责任链(多个处理器都可能处理,请求继续传递)。
五、场景三:Web 请求过滤器链(请求处理链场景)
业务背景:Web 应用需要对请求进行多层过滤:字符编码过滤、登录权限校验、敏感词过滤、日志记录。每个过滤器判断请求是否满足条件:不满足则直接拦截返回,满足则传递给下一个过滤器。使用责任链模式将各过滤器解耦。
说明:这是责任链模式在请求处理链场景中的典型应用(Servlet Filter、Spring Interceptor 均使用类似设计),也是软考上午题中 "过滤器链" 场景的代表。
5.1 代码实现
java
// 请求对象
public class HttpRequest {
private String url;
private String token;
private String content;
public HttpRequest(String url, String token, String content) {
this.url = url;
this.token = token;
this.content = content;
}
public String getUrl() { return url; }
public String getToken() { return token; }
public String getContent() { return content; }
}
// Handler:抽象过滤器
public abstract class Filter {
protected Filter nextFilter; // 下一个过滤器
public void setNextFilter(Filter nextFilter) {
this.nextFilter = nextFilter;
}
// 模板方法:处理请求
public void doFilter(HttpRequest request) {
if (handle(request)) { // 当前过滤器校验通过
if (nextFilter != null) {
nextFilter.doFilter(request); // 传递给下一个
} else {
System.out.println("【系统】所有过滤器通过,请求到达目标资源");
}
} else {
System.out.println("【系统】请求被 " + this.getClass().getSimpleName() + " 拦截");
}
}
// 子类实现具体的校验逻辑,返回 true 表示通过,false 表示拦截
protected abstract boolean handle(HttpRequest request);
}
// ConcreteHandler:字符编码过滤器
public class EncodingFilter extends Filter {
@Override
protected boolean handle(HttpRequest request) {
System.out.println("【编码过滤器】检查字符编码...");
return true; // 总是通过
}
}
// ConcreteHandler:登录校验过滤器
public class AuthFilter extends Filter {
@Override
protected boolean handle(HttpRequest request) {
System.out.println("【权限过滤器】校验 Token: " + request.getToken());
if (request.getToken() == null || request.getToken().isEmpty()) {
System.out.println("【权限过滤器】Token 无效,拒绝访问");
return false;
}
return true;
}
}
// ConcreteHandler:敏感词过滤器
public class SensitiveFilter extends Filter {
@Override
protected boolean handle(HttpRequest request) {
System.out.println("【敏感词过滤器】检查内容...");
if (request.getContent().contains("敏感词")) {
System.out.println("【敏感词过滤器】包含敏感词,拒绝访问");
return false;
}
return true;
}
}
// ConcreteHandler:日志过滤器
public class LogFilter extends Filter {
@Override
protected boolean handle(HttpRequest request) {
System.out.println("【日志过滤器】记录访问日志: " + request.getUrl());
return true;
}
}
// Client
public class Client {
public static void main(String[] args) {
// 创建过滤器
Filter encoding = new EncodingFilter();
Filter auth = new AuthFilter();
Filter sensitive = new SensitiveFilter();
Filter log = new LogFilter();
// 组装过滤器链:编码 -> 权限 -> 敏感词 -> 日志
encoding.setNextFilter(auth);
auth.setNextFilter(sensitive);
sensitive.setNextFilter(log);
// 正常请求
System.out.println("=== 正常请求 ===");
HttpRequest req1 = new HttpRequest("/api/data", "token123", "正常内容");
encoding.doFilter(req1);
System.out.println();
// 无 Token 请求
System.out.println("=== 无权限请求 ===");
HttpRequest req2 = new HttpRequest("/api/data", "", "正常内容");
encoding.doFilter(req2);
System.out.println();
// 包含敏感词请求
System.out.println("=== 敏感词请求 ===");
HttpRequest req3 = new HttpRequest("/api/data", "token123", "包含敏感词的内容");
encoding.doFilter(req3);
}
}
六、三种场景对比与演进思路
| 维度 | 场景一:请假审批 | 场景二:日志过滤 | 场景三:Web 过滤器 |
|---|---|---|---|
| Handler | Approver(审批者) |
Logger(日志处理器) |
Filter(过滤器) |
| ConcreteHandler | Supervisor、Manager、GeneralManager |
ConsoleLogger、FileLogger、EmailLogger |
EncodingFilter、AuthFilter、SensitiveFilter、LogFilter |
| 请求对象 | LeaveRequest(请假请求) |
LogMessage(日志消息) |
HttpRequest(HTTP 请求) |
| 处理逻辑 | 能处理则处理,处理完即停(纯责任链) | 能处理则处理,继续传递(不纯责任链) | 校验通过则传递,不通过则拦截(拦截器链) |
| 链的组装 | 客户端手动 setSuccessor |
客户端手动 setNextLogger |
客户端手动 setNextFilter |
| 传递条件 | 当前处理者无权处理 | 无条件传递(每个都处理自己级别) | 当前校验通过 |
| 停止条件 | 请求被处理 或 链尾无后继者 | 链尾无后继者 | 校验失败 或 链尾无后继者 |
| 新增扩展 | 插入新审批级别 | 插入新日志处理器 | 插入新过滤器 |
| 软考考法 | 下午大题(补全审批转发逻辑) | 下午大题(补全多级处理逻辑) | 上午选择题(识别过滤器链) |
七、责任链模式 vs 观察者模式 vs 装饰模式:核心对比
| 对比项 | 责任链模式 | 观察者模式 | 装饰模式 |
|---|---|---|---|
| 模式分类 | 行为型 | 行为型 | 结构型 |
| 核心思想 | 请求沿链传递,直到被处理 | 状态变化广播通知 所有观察者 | 动态包装 对象,层层增强功能 |
| 对象关系 | Handler 之间 链式引用(successor) | Subject 与 Observer 是 一对多关联 | Decorator 与 Component 是 组合关系(is-a + has-a) |
| 请求流向 | 单向传递(沿链依次尝试) | 单向广播(Subject → Observer) | 单向包装(层层调用,最终到达被装饰对象) |
| 处理对象 | 多个对象 竞争 处理同一个请求 | 多个对象 响应 同一个状态变化 | 多个对象 共同 完成一个功能(层层增强) |
| 客户端角色 | 向链头提交请求,不关心谁处理 | 触发状态变化,不直接通知观察者 | 创建装饰链,调用最外层装饰对象 |
| 能否中断 | 能(请求被处理或校验失败即中断传递) | 不能(广播给所有观察者) | 不能(必须层层传递到被装饰对象) |
| 典型应用 | 审批流程、异常处理链、过滤器链 | 消息订阅、事件监听、MVC 模型通知 | IO 流包装、UI 组件美化、权限包装 |
| 软考判断 | 看到 "请求沿链传递"、"多级处理"、"审批流程"、"过滤器" → 责任链 | 看到 "一对多通知"、"状态变化自动更新"、"订阅发布" → 观察者 | 看到 "动态包装"、"层层增强"、"装饰器套装饰器" → 装饰 |
记忆口诀:责任链是 "逐级上报"(请求沿链传递直到有人处理),观察者是 "群发通知"(状态变了广播给所有人),装饰是 "层层包装"(给对象穿多层衣服)。
八、软考高频考点与易混淆辨析
8.1 高频考点
| 考点 | 内容 |
|---|---|
| 模式分类 | 行为型模式(GoF 23 正式成员) |
| 核心三角色 | Handler(抽象处理者)、ConcreteHandler(具体处理者)、Client(客户端) |
| 核心思想 | 使多个对象都有机会处理请求,避免请求发送者与接收者耦合,将对象连成链传递请求 |
| 链式引用 | Handler 持有 successor(或 next)字段,指向下一个处理者 |
| 请求转发 | ConcreteHandler 的 handleRequest() 中:能处理则处理,不能处理则 successor.handleRequest() |
| 纯责任链 | 请求 只能被一个 处理者处理,处理完即停止传递(如审批流程) |
| 不纯责任链 | 请求 可被多个 处理者处理,处理完继续传递(如日志处理器) |
| 与观察者模式区别 | 责任链:请求沿链传递,竞争处理 ;观察者:状态变化广播通知 |
| 与装饰模式区别 | 责任链:多个对象依次尝试 处理请求;装饰:多个对象层层包装增强功能 |
| 与策略模式区别 | 责任链:多个对象依次尝试 ,直到有人处理;策略:客户端选择一种 算法 |
| 适用场景 | ① 多个对象可处理同一请求,具体处理者运行时确定 ② 需要动态指定处理者集合 ③ 不希望请求发送者与接收者紧耦合 |
| 不适用场景 | 请求处理逻辑简单,只有一个处理者;处理者之间需要共享大量状态(链式传递效率低) |
8.2 易混淆辨析:责任链 vs 观察者 vs 装饰
| 对比项 | 责任链模式 | 观察者模式 | 装饰模式 |
|---|---|---|---|
| UML 结构 | Handler 链式引用 successor(单链表) | Subject 聚合多个 Observer(一对多) | Decorator 组合 Component 并继承 Component(包装) |
| 核心方法 | handleRequest()(判断 + 转发) |
attach()/detach()/notify()、update() |
operation()(层层委托到被装饰对象) |
| 调用方式 | 客户端调用链头,沿链传递 | Subject 状态变化自动调用 | 客户端调用最外层 Decorator |
| 中断性 | 可中断(处理完或校验失败即停) | 不可中断(广播给所有观察者) | 不可中断(必须层层传递到 Component) |
| 扩展方式 | 新增 ConcreteHandler,插入链中 | 新增 Observer,注册到 Subject | 新增 Decorator,包装现有对象 |
| 软考考法 | 代码填空(补全 successor.handleRequest() + 判断条件) |
代码填空(补全 attach/detach/notify + update) |
代码填空(补全 super.operation() + 新增行为) |
九、真题风格模拟与代码填空
模拟题 1(上午选择题)
以下关于责任链模式的叙述中,正确的是()。
A. 责任链模式属于结构型模式,主要用于动态包装对象以增强功能
B. 在责任链模式中,客户端必须知道哪个具体处理者能处理请求,并直接调用该处理者
C. 责任链模式使多个对象都有机会处理请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
D. 责任链模式与观察者模式的核心区别在于:责任链模式是状态变化广播通知,观察者模式是请求沿链传递
答案:C
解析:
-
A 错误:责任链模式属于 行为型 模式,不是结构型。动态包装对象以增强功能的是 装饰模式。
-
B 错误:责任链模式的 核心目的 就是解耦客户端与处理者。客户端 不需要知道 哪个处理者能处理请求,只需要把请求交给链头即可。
-
C 正确:这是责任链模式的核心定义(GoF 原书定义)。
-
D 错误:说反了。责任链模式是 请求沿链传递 ,观察者模式是 状态变化广播通知。
模拟题 2(下午代码填空 --- 补全责任链转发逻辑)
某系统使用责任链模式处理异常。
ExceptionHandler是抽象处理者,MinorHandler处理一般异常,CriticalHandler处理严重异常。请补全(1)~(4)。
java
// 异常请求类
class ExceptionRequest {
private int severity; // 严重程度:1-一般,2-严重
private String message;
public ExceptionRequest(int severity, String message) {
this.severity = severity;
this.message = message;
}
public int getSeverity() { return severity; }
public String getMessage() { return message; }
}
// Handler
abstract class ExceptionHandler {
protected ExceptionHandler (1)______; // 后继者
public void setSuccessor(ExceptionHandler successor) {
this.successor = successor;
}
public abstract void (2)______(ExceptionRequest request);
}
// ConcreteHandler:处理一般异常
class MinorHandler extends ExceptionHandler {
@Override
public void handleRequest(ExceptionRequest request) {
if (request.getSeverity() == 1) {
System.out.println("【一般处理器】处理异常: " + request.getMessage());
} else if ((3)______ != null) {
(4)______; // 转发给后继者
} else {
System.out.println("【一般处理器】无法处理,且无后继者");
}
}
}
// ConcreteHandler:处理严重异常
class CriticalHandler extends ExceptionHandler {
@Override
public void handleRequest(ExceptionRequest request) {
if (request.getSeverity() == 2) {
System.out.println("【严重处理器】处理异常: " + request.getMessage());
} else {
System.out.println("【严重处理器】异常级别不匹配");
}
}
}
public class Client {
public static void main(String[] args) {
ExceptionHandler minor = new MinorHandler();
ExceptionHandler critical = new CriticalHandler();
minor.setSuccessor(critical);
minor.handleRequest(new ExceptionRequest(1, "数据库连接超时"));
minor.handleRequest(new ExceptionRequest(2, "内存溢出"));
}
}
答案:
-
(1)
successor -
(2)
handleRequest -
(3)
successor -
(4)
successor.handleRequest(request)
阅卷要点:
(1) 必须是
successor(或nextHandler)。这是 Handler 中指向链上下一个处理者的标准字段名。如果写next或handler可能给分,但successor是标准答案。(2) 必须是
handleRequest。这是处理请求的标准方法名。如果写process或handle可能给分,但handleRequest是 GoF 标准方法名。(3) 必须是
successor。判断是否有后继者可以转发请求。如果写this或next会 零分。(4) 必须是
successor.handleRequest(request)。这是责任链的核心:当前处理者无法处理时,将请求转发给后继者。如果写successor.process(request)可能给分(如果后继者定义了该方法),但handleRequest是标准答案。如果写return或throw会 零分。
模拟题 3(下午代码填空 --- 补全不纯责任链)
某系统使用责任链模式实现多级折扣计算。
DiscountHandler处理折扣请求,各级处理者可以叠加折扣。请补全(1)~(2)。
java
// 折扣请求
class DiscountRequest {
private double amount;
public DiscountRequest(double amount) { this.amount = amount; }
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
}
// Handler
abstract class DiscountHandler {
protected DiscountHandler next;
protected double discountRate;
public DiscountHandler(double discountRate) {
this.discountRate = discountRate;
}
public void setNext(DiscountHandler next) {
this.next = next;
}
public void handle(DiscountRequest request) {
// 应用当前折扣
double original = request.getAmount();
double discounted = original * (1 - discountRate);
request.setAmount(discounted);
System.out.println("【" + this.getClass().getSimpleName() +
"】应用折扣 " + (discountRate * 100) + "%,金额: " + original + " -> " + discounted);
// 继续传递给下一个处理器(不纯责任链)
if ((1)______ != null) {
(2)______;
}
}
}
// ConcreteHandler:会员折扣
class MemberDiscount extends DiscountHandler {
public MemberDiscount() { super(0.1); } // 9折
}
// ConcreteHandler:优惠券折扣
class CouponDiscount extends DiscountHandler {
public CouponDiscount() { super(0.2); } // 8折
}
public class Client {
public static void main(String[] args) {
DiscountHandler member = new MemberDiscount();
DiscountHandler coupon = new CouponDiscount();
member.setNext(coupon);
DiscountRequest request = new DiscountRequest(1000);
member.handle(request);
System.out.println("最终金额: " + request.getAmount()); // 1000 * 0.9 * 0.8 = 720
}
}
答案:
-
(1)
next -
(2)
next.handle(request)
阅卷要点:
(1) 必须是
next。判断是否有下一个处理器可以继续处理。如果写successor也可能给分,但题目中定义的是next字段,所以next是标准答案。(2) 必须是
next.handle(request)。不纯责任链的核心是每个处理者处理完后,无论是否处理了请求,都继续传递给下一个。如果写next.process(request)可能给分,但handle是标准答案。如果写return会 零分。
十、常见陷阱与注意事项
陷阱 1:误认为责任链是结构型模式
责任链模式属于 行为型 模式,因为它关注的是 对象之间的行为交互 (请求沿链传递),而不是类与类之间的结构关系。软考上午题如果问"以下属于结构型模式的是",选项里出现"责任链" 不能选。
陷阱 2:客户端直接调用具体处理者
这是软考 最高频的失分点。责任链模式的核心就是解耦客户端与处理者:
- ❌ 错误:
java
// 客户端直接判断该找谁处理
if (request.getDays() <= 3) {
supervisor.handle(request);
} else if (request.getDays() <= 7) {
manager.handle(request);
} else {
generalManager.handle(request);
}
- ✅ 正确:
java
// 客户端只认识链头,请求沿链自动传递
supervisor.handleRequest(request);
如果客户端需要判断条件来决定调用哪个处理者,那就失去了责任链模式的意义,退化成普通的 if-else 逻辑。
陷阱 3:与装饰模式混淆
这是软考 最难的辨析题 之一。两者的核心区别:
表格
| 维度 | 责任链模式 | 装饰模式 |
|---|---|---|
| 模式分类 | 行为型 | 结构型 |
| 核心目的 | 选择 一个合适的对象处理请求 | 增强 对象的功能,所有装饰者都参与 |
| 对象关系 | Handler 之间是 同级 的链式引用(竞争关系) | Decorator 与 Component 是 包装与被包装(增强关系) |
| 调用链 | 请求被 一个 对象处理(或都不处理) | 请求被 所有 装饰者层层处理(最终到达 Component) |
| 中断性 | 可中断(处理完即停) | 不可中断(必须层层传递) |
| 类结构 | 多个 ConcreteHandler 继承 Handler | Decorator 继承 Component 并组合 Component |
终极区分方法:看请求的最终结果。
-
如果请求最终只被一个对象处理,其他对象只是"转手" → 责任链。
-
如果请求被所有对象层层加工,最终返回增强后的结果 → 装饰。
陷阱 4:与观察者模式混淆
表格
| 维度 | 责任链模式 | 观察者模式 |
|---|---|---|
| 触发源 | 客户端 主动提交 请求 | Subject 状态变化 自动触发 |
| 通信方向 | 请求沿链 单向传递(Handler → successor) | 通知 单向广播(Subject → Observer) |
| 处理对象 | 多个 Handler 竞争 处理同一个请求 | 多个 Observer 各自独立响应 状态变化 |
| 对象关系 | Handler 之间形成 链(线性结构) | Subject 与 Observer 形成 星型(一对多) |
如果类图里是线性链式结构,且请求沿链传递 → 责任链。如果类图里是一个中心对象向多个对象广播 → 观察者。
陷阱 5:链没有正确组装导致 NullPointerException
如果链没有正确组装,某个处理者的 successor 为 null,而请求又无法被当前处理者处理,会导致空指针或请求丢失。
- ❌ 错误:
java
Approver supervisor = new Supervisor("张");
Approver manager = new Manager("李");
// 忘记设置后继者!supervisor.setSuccessor(manager);
supervisor.handleRequest(request); // 如果 supervisor 处理不了,successor 为 null,请求丢失
- ✅ 正确:
java
Approver supervisor = new Supervisor("张");
Approver manager = new Manager("李");
Approver general = new GeneralManager("王");
supervisor.setSuccessor(manager);
manager.setSuccessor(general); // 链尾也要设置,虽然 general 的 successor 为 null
软考中,链的组装通常在客户端完成,代码填空时需要注意 设置 successor 的代码。
陷阱 6:不纯责任链中忘记继续传递
在不纯责任链(如日志处理器)中,当前处理者处理完请求后,必须继续传递给下一个:
- ❌ 错误:
java
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message); // 处理完就停了,不传递!
}
}
- ✅ 正确:
java
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) { // 无论是否处理,都继续传递
nextLogger.logMessage(level, message);
}
}
软考中,如果题目描述的是 "多个处理器可能同时处理"(如日志、折扣叠加),必须在处理逻辑后继续传递请求。
陷阱 7:责任链变成死循环
如果链的组装出现循环引用(如 A 的后继是 B,B 的后继又是 A),会导致请求无限循环。
- ❌ 错误:
java
a.setSuccessor(b);
b.setSuccessor(a); // 循环引用!
- ✅ 正确:
java
a.setSuccessor(b);
b.setSuccessor(c);
c.setSuccessor(null); // 链尾的后继为 null
软考中,虽然这种情况较少出现,但如果题目问"责任链模式的注意事项",选项中出现 "避免循环引用导致死循环" → 这是正确答案。
十一、总结
| 要点 | 内容 |
|---|---|
| 定义 | 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止 |
| 分类 | 行为型模式(GoF 23 正式成员) |
| 核心三角色 | Handler(抽象处理者)、ConcreteHandler(具体处理者)、Client(客户端) |
| 核心思想 | 将请求的发送者与处理者解耦,请求沿处理者链传递,直到被处理 |
| 链式引用 | Handler 持有 successor(或 next)字段,指向下一个处理者 |
| 请求转发 | ConcreteHandler 能处理则处理,不能处理则 successor.handleRequest(request) |
| 纯责任链 | 请求 只能被一个 处理者处理,处理完即停止传递(如审批流程) |
| 不纯责任链 | 请求 可被多个 处理者处理,处理完继续传递(如日志处理器、折扣叠加) |
| 与观察者模式区别 | 责任链:请求沿链传递,竞争处理 ;观察者:状态变化广播通知 |
| 与装饰模式区别 | 责任链:多个对象依次尝试 处理请求;装饰:多个对象层层包装增强功能 |
| 与策略模式区别 | 责任链:多个对象依次尝试 直到有人处理;策略:客户端选择一种 算法 |
| 适用场景 | ① 多个对象可处理同一请求,具体处理者运行时确定 ② 需要动态指定处理者集合 ③ 不希望请求发送者与接收者紧耦合 |
| 注意事项 | 避免链未组装导致请求丢失;避免循环引用导致死循环;区分纯/不纯责任链 |
| 软考重点 | 代码填空(补全 successor 字段 + handleRequest 转发逻辑 + 判断条件);与装饰、观察者模式的辨析;纯/不纯责任链的识别 |
| 答题技巧 | 看到 "请求沿链传递"、"多级处理"、"审批流程"、"过滤器链"、"直到有人处理" → 责任链;看到 Handler 持有 successor 并转发请求 → 确认责任链 |
系列预告 :下一篇将讲 命令模式 ------ 当需要将请求封装为对象,从而支持请求的排队、记录日志、撤销重做等操作时,如何将调用操作的对象与知道如何实现该操作的对象解耦。命令模式与备忘录模式、策略模式的辨析是软考高频考点,咱们下回重点拆解。咱们下回见。