【软考设计模式】责任链模式:请求传递的多级处理与发送接收解耦精讲

系列定位:软考软件设计师 / 系统架构设计师 --- 行为型模式专题第 10 讲

考察分值:上午题 1-2 分,下午题常作为代码填空或类图识别出现

难度等级:⭐⭐⭐☆☆(结构清晰,Handler 链式引用与请求转发逻辑是填空重点)


一、考纲定位与模式定义
1.1 考纲要求

责任链模式在软考中属于 行为型模式 的基础内容。考察形式包括:

  • 上午选择题:判断场景描述所属模式;识别责任链模式与观察者模式、装饰模式的区别;判断 Handler、ConcreteHandler、Client 三个角色;识别 "请求沿链传递直到被处理" 的设计意图

  • 下午设计题 :补全 Handler 抽象类中 setSuccessor()handleRequest() 的代码;补全 ConcreteHandler 中判断能否处理请求以及转发给后继者的逻辑;识别类图中 Handler 之间的链式引用关系(通常用 nextsuccessor 字段)

1.2 模式定义

责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

核心意图 :当一个系统需要 处理某种请求 ,且存在 多个对象 都有能力处理该请求,但具体由哪个对象处理取决于请求的类型或条件时,如果客户端直接决定调用哪个处理对象,会导致:

  1. 客户端与处理对象紧耦合,需要知道所有处理对象的细节

  2. 新增处理对象时需要修改客户端代码

  3. 处理逻辑散落在客户端,难以维护

责任链模式将这些处理对象 连成一条链 ,客户端只需要把请求交给链头的第一个对象。每个对象判断自己能否处理:能处理就直接处理,不能处理就把请求 转发给链上的下一个对象。请求在链上传递,直到被某个对象处理,或者传到链尾也没人处理。

通俗理解

你去医院看病,先挂门诊号。门诊医生看了说 "这病我看不了,你去找专科主任";专科主任看了说 "这病太复杂,你去找专家教授";专家教授看了说 "这我能治",然后给你开了药。你(客户端)不需要一开始就知道自己该找谁,只需要把病历交给门诊医生(链头),病历会沿着医生级别这条链自动传递,直到有人能治。

如果没有责任链,你得自己研究每个医生的专长,然后判断该挂哪个号------这就是客户端与处理对象的紧耦合。


二、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() 模板方法。

  • SupervisorManagerGeneralManager(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 SupervisorManagerGeneralManager ConsoleLoggerFileLoggerEmailLogger EncodingFilterAuthFilterSensitiveFilterLogFilter
请求对象 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 中指向链上下一个处理者的标准字段名。如果写 nexthandler 可能给分,但 successor 是标准答案。

  • (2) 必须是 handleRequest。这是处理请求的标准方法名。如果写 processhandle 可能给分,但 handleRequest 是 GoF 标准方法名。

  • (3) 必须是 successor。判断是否有后继者可以转发请求。如果写 thisnext零分

  • (4) 必须是 successor.handleRequest(request)。这是责任链的核心:当前处理者无法处理时,将请求转发给后继者。如果写 successor.process(request) 可能给分(如果后继者定义了该方法),但 handleRequest 是标准答案。如果写 returnthrow零分


模拟题 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 并转发请求 → 确认责任链

系列预告 :下一篇将讲 命令模式 ------ 当需要将请求封装为对象,从而支持请求的排队、记录日志、撤销重做等操作时,如何将调用操作的对象与知道如何实现该操作的对象解耦。命令模式与备忘录模式、策略模式的辨析是软考高频考点,咱们下回重点拆解。咱们下回见。

相关推荐
ttod_qzstudio1 小时前
【软考设计模式】备忘录模式:对象状态的捕获与无损恢复精讲
设计模式·备忘录模式
2501_914245932 小时前
C语言设计模式详解:从理论到实践的完整指南
c语言·开发语言·设计模式
CaffeinePro6 小时前
四⼤极简架构原则KISS/DRY/YAGNI/LOD
设计模式·架构
霸道流氓气质7 小时前
SpringBoot中设计模式组合使用示例-策略、模板、观察者、门面、工厂、单例。
spring boot·后端·设计模式
ttod_qzstudio9 小时前
【软考设计模式】观察者模式:一对多依赖的自动通知与订阅解耦精讲
观察者模式·设计模式
ttod_qzstudio11 小时前
【软考设计模式】命令模式:请求封装与调用接收解耦的精讲
设计模式·命令模式
春卷同学11 小时前
HarmonyOS掌上记账APP开发实践第5篇:鸿蒙 EventBus 设计模式 — MoneyTrack 跨模块通信解密
华为·设计模式·harmonyos
春卷同学13 小时前
HarmonyOS掌上记账APP开发实践第3篇:接口与实现分离 — MoneyTrack Types.ets 与 Models.ets 的双层设计模式
设计模式
NG4771 天前
【软件设计与体系结构】-策略设计模式
java·设计模式·软件工程