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

系列定位:软考软件设计师 / 系统架构设计师 — 行为型模式专题第 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 代码实现
// 请假请求类
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 代码实现
// 日志请求类
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 代码实现
// 请求对象
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 过滤器
HandlerApprover(审批者)Logger(日志处理器)Filter(过滤器)
ConcreteHandlerSupervisorManagerGeneralManagerConsoleLoggerFileLoggerEmailLoggerEncodingFilterAuthFilterSensitiveFilterLogFilter
请求对象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)。

// 异常请求类
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)。

// 折扣请求
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:客户端直接调用具体处理者

这是软考 最高频的失分点。责任链模式的核心就是解耦客户端与处理者:

  • ❌ 错误:

// 客户端直接判断该找谁处理
if (request.getDays() <= 3) {
    supervisor.handle(request);
} else if (request.getDays() <= 7) {
    manager.handle(request);
} else {
    generalManager.handle(request);
}
  • ✅ 正确:

// 客户端只认识链头,请求沿链自动传递
supervisor.handleRequest(request);

如果客户端需要判断条件来决定调用哪个处理者,那就失去了责任链模式的意义,退化成普通的 if-else 逻辑。

陷阱 3:与装饰模式混淆

这是软考 最难的辨析题 之一。两者的核心区别:

表格

维度责任链模式装饰模式
模式分类行为型结构型
核心目的选择 一个合适的对象处理请求增强 对象的功能,所有装饰者都参与
对象关系Handler 之间是 同级 的链式引用(竞争关系)Decorator 与 Component 是 包装与被包装(增强关系)
调用链请求被 一个 对象处理(或都不处理)请求被 所有 装饰者层层处理(最终到达 Component)
中断性可中断(处理完即停)不可中断(必须层层传递)
类结构多个 ConcreteHandler 继承 HandlerDecorator 继承 Component 并组合 Component

终极区分方法:看请求的最终结果。

  • 如果请求最终只被一个对象处理,其他对象只是"转手" → 责任链。

  • 如果请求被所有对象层层加工,最终返回增强后的结果 → 装饰。

陷阱 4:与观察者模式混淆

表格

维度责任链模式观察者模式
触发源客户端 主动提交 请求Subject 状态变化 自动触发
通信方向请求沿链 单向传递(Handler → successor)通知 单向广播(Subject → Observer)
处理对象多个 Handler 竞争 处理同一个请求多个 Observer 各自独立响应 状态变化
对象关系Handler 之间形成 (线性结构)Subject 与 Observer 形成 星型(一对多)

如果类图里是线性链式结构,且请求沿链传递 → 责任链。如果类图里是一个中心对象向多个对象广播 → 观察者。

陷阱 5:链没有正确组装导致 NullPointerException

如果链没有正确组装,某个处理者的 successor 为 null,而请求又无法被当前处理者处理,会导致空指针或请求丢失。

  • ❌ 错误:

Approver supervisor = new Supervisor("张");
Approver manager = new Manager("李");
// 忘记设置后继者!supervisor.setSuccessor(manager);

supervisor.handleRequest(request);  // 如果 supervisor 处理不了,successor 为 null,请求丢失
  • ✅ 正确:

Approver supervisor = new Supervisor("张");
Approver manager = new Manager("李");
Approver general = new GeneralManager("王");

supervisor.setSuccessor(manager);
manager.setSuccessor(general);  // 链尾也要设置,虽然 general 的 successor 为 null

软考中,链的组装通常在客户端完成,代码填空时需要注意 设置 successor 的代码。

陷阱 6:不纯责任链中忘记继续传递

在不纯责任链(如日志处理器)中,当前处理者处理完请求后,必须继续传递给下一个:

  • ❌ 错误:

public void logMessage(int level, String message) {
    if (this.level <= level) {
        write(message);  // 处理完就停了,不传递!
    }
}
  • ✅ 正确:

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),会导致请求无限循环。

  • ❌ 错误:

a.setSuccessor(b);
b.setSuccessor(a);  // 循环引用!
  • ✅ 正确:

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值