零基础设计模式——行为型模式 - 责任链模式

第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern)

欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。

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

责任链模式 (Chain of Responsibility Pattern)

"使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。" (Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.)

想象一下公司里的报销审批流程。一个员工提交了报销申请:

  1. 如果金额较小(比如500元以下),可能直接由其直属经理(第一个处理者)审批即可。
  2. 如果金额稍大(比如500元到2000元),直属经理审批后,还需要部门主管(第二个处理者)审批。
  3. 如果金额更大(比如2000元以上),部门主管审批后,可能还需要财务总监(第三个处理者)审批。

员工只需要提交申请,申请会沿着这条"审批链"传递,直到遇到能够完全处理它(或者批准,或者拒绝)的审批人,或者链条结束。

  • 请求 (Request):报销申请。
  • 处理者 (Handler):直属经理、部门主管、财务总监。
  • 链 (Chain):由这些审批人构成的审批顺序。

1. 目的 (Intent)

责任链模式的主要目的:

  1. 解耦发送者和接收者:请求的发送者不需要知道是哪个具体对象会处理它的请求。它只需要将请求发送到链的头部即可。
  2. 动态组合责任:可以动态地改变链中的处理者顺序或增删处理者,而无需修改客户端代码。
  3. 请求的灵活处理:每个处理者都可以决定自己是否处理该请求、是否将请求传递给下一个处理者,或者两者都做。

2. 生活中的例子 (Real-world Analogy)

  • 客服电话系统

    • 你拨打客服电话,首先可能是一个自动语音应答系统(第一处理者)尝试解决你的问题。
    • 如果解决不了,它会将你转接到初级人工客服(第二处理者)。
    • 如果初级客服也无法解决,可能会转接到高级客服或技术支持(第三处理者)。
      你的问题沿着这条服务链传递,直到被解决。
  • 异常处理机制 (try-catch-finally)

    • 在很多编程语言中,try 块中的代码如果抛出异常,会先被最近的 catch 块尝试捕获和处理。
    • 如果第一个 catch 块不能处理该类型的异常,或者它选择重新抛出,异常会向上传播到外层的 catch 块(如果存在的话),形成一个异常处理链。
  • 事件冒泡/捕获 (Event Bubbling/Capturing in Web Development)

    • 在HTML DOM中,当一个元素上发生事件(如点击),事件会从目标元素开始,逐级向上传播到其父元素,直到文档根节点(事件冒泡),或者从文档根节点向下传播到目标元素(事件捕获)。
    • 每一级父元素都有机会处理这个事件。
  • 中间件 (Middleware in Web Frameworks)

    • 像 Express.js, Django, ASP.NET Core 等Web框架中的中间件。一个HTTP请求会依次通过一系列注册的中间件(如日志记录、身份验证、数据压缩、路由处理等)。
    • 每个中间件都可以处理请求、修改请求/响应,或者将请求传递给链中的下一个中间件。

3. 结构 (Structure)

责任链模式通常包含以下角色:

  1. Handler (处理者接口/抽象类) :定义一个处理请求的接口。通常会包含一个指向链中下一个处理者的引用(successor)。它声明了一个处理请求的方法(如 handleRequest())。
  2. ConcreteHandler (具体处理者):实现 Handler 接口。它负责处理它感兴趣的请求。如果它可以处理该请求,就处理之;否则,它会将请求转发给它的后继者。
  3. Client (客户端) :创建处理者链,并发起请求到链上的某个处理者(通常是链的第一个处理者)。

    工作流程
  • 客户端创建一个处理者链,并设置每个处理者的后继者。
  • 客户端向链的第一个处理者发送请求。
  • 处理者接收到请求后:
    • 判断自己是否能处理该请求。
    • 如果能处理,则处理请求。此时,它可以选择是否将请求继续传递给后继者(取决于具体需求,有些链在请求被处理后即终止,有些则允许继续传递)。
    • 如果不能处理,则将请求传递给其后继者(如果后继者存在)。
  • 请求沿着链传递,直到被某个处理者处理,或者到达链的末尾仍未被处理。

4. 适用场景 (When to Use)

  • 有多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态确定。
  • 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 需要动态指定一组对象处理请求的顺序。
  • 当你想让请求的发送者和接收者解耦时。
  • 当处理请求的对象集合应当动态指定时。

5. 优缺点 (Pros and Cons)

优点:

  1. 降低耦合度:请求的发送者和接收者之间解耦。发送者不需要知道谁将处理请求,也不需要知道链的结构。
  2. 增强了对象的指派职责的灵活性:可以通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  3. 增加新的请求处理类很方便:只需创建新的 Handler 类并将其加入链中即可,符合开闭原则。
  4. 每个处理者职责清晰:每个具体处理者只需要关注自己能处理的请求。

缺点:

  1. 请求不一定会被处理:由于请求在链上发送,如果链配置不当或者没有任何处理者能够处理该请求,请求可能会落空(到达链尾仍未被处理)。
  2. 不易观察运行时的特征:如果链条比较长,调试时可能不容易追踪请求的实际处理流程。
  3. 可能影响性能:请求需要从链头开始遍历,如果链过长,且大部分请求都需要由链尾的处理者处理,性能可能会受到影响。
  4. 链的创建和配置可能比较复杂:需要正确设置每个处理者的后继者。

6. 实现方式 (Implementations)

让我们以一个简单的日志系统为例。不同级别的日志消息(INFO, DEBUG, ERROR)由不同的记录器处理。

请求类 (LogRequest - 可选,或直接用参数)

这里我们直接用参数(日志级别和消息内容)传递,不单独创建请求类。

处理者接口 (Logger - Handler)
go 复制代码
// logger.go (Handler interface)
package logging

const (
	LevelInfo  = 1
	LevelDebug = 2
	LevelError = 3
)

// Logger 处理者接口
type Logger interface {
	SetNext(logger Logger) Logger // 设置下一个处理者,并返回下一个处理者方便链式调用
	LogMessage(level int, message string) // 处理日志消息
	write(message string) // 具体的写日志方法,由具体处理者实现
}
java 复制代码
// Logger.java (Handler abstract class)
package com.example.logging;

public abstract class Logger {
    public static final int INFO = 1;
    public static final int DEBUG = 2;
    public static final int ERROR = 3;

    protected int level; // 当前处理器能处理的级别
    protected Logger nextLogger; // 责任链中的下一个元素

    public Logger setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
        return this.nextLogger; // 返回下一个logger,方便链式设置
    }

    public void logMessage(int level, String message) {
        if (this.level <= level) { // 如果当前处理器的级别允许处理该消息
            write(message);
        }
        if (nextLogger != null) { // 如果有下一个处理器,则传递下去
            nextLogger.logMessage(level, message);
        }
    }

    // 抽象方法,由子类实现具体的日志写入操作
    abstract protected void write(String message);
}
具体处理者 (InfoLogger, DebugLogger, ErrorLogger - ConcreteHandler)
go 复制代码
// abstract_logger.go (Base for Concrete Handlers - Go doesn't have abstract classes, use struct embedding)
package logging

// AbstractLogger 基础结构,包含下一个处理者的引用
type AbstractLogger struct {
	level    int
	next     Logger
}

func (al *AbstractLogger) SetNext(logger Logger) Logger {
	al.next = logger
	return logger
}

// LogMessage 默认实现:如果级别匹配则自己写,然后传递给下一个
func (al *AbstractLogger) LogMessage(level int, message string) {
	if al.level <= level {
		al.write(message) // 调用具体实现类的 write
	}
	if al.next != nil {
		al.next.LogMessage(level, message)
	}
}

// write 是一个需要被具体 Logger 实现的方法,但由于 Go 的限制,
// 我们不能在这里定义一个抽象的 write。具体 Logger 需要自己实现 write,
// 并在 LogMessage 中调用它,或者像上面那样,让 AbstractLogger 的 LogMessage 调用一个 write 方法,
// 这个 write 方法必须是具体 Logger 类型的一部分。
// 为了简单,我们让具体 Logger 实现 Logger 接口,并在 LogMessage 中直接调用自己的 write。
// 或者,我们可以让具体 Logger 嵌入 AbstractLogger,并重写 LogMessage 或提供 write。

// --- 具体实现 ---

// info_logger.go
package logging

import "fmt"

type InfoLogger struct {
	AbstractLogger
}

func NewInfoLogger(level int) *InfoLogger {
	il := &InfoLogger{}
	il.AbstractLogger.level = level
	return il
}

// write 实现具体的日志写入逻辑
func (il *InfoLogger) write(message string) {
	fmt.Println("INFO Logger: " + message)
}

// LogMessage (可以重写以改变行为,例如只处理自己的级别然后停止)
// 如果不重写,则使用 AbstractLogger 的 LogMessage 行为

// debug_logger.go
package logging

import "fmt"

type DebugLogger struct {
	AbstractLogger
}

func NewDebugLogger(level int) *DebugLogger {
	dl := &DebugLogger{}
	dl.AbstractLogger.level = level
	return dl
}

func (dl *DebugLogger) write(message string) {
	fmt.Println("DEBUG Logger: " + message)
}

// error_logger.go
package logging

import "fmt"

type ErrorLogger struct {
	AbstractLogger
}

func NewErrorLogger(level int) *ErrorLogger {
	el := &ErrorLogger{}
	el.AbstractLogger.level = level
	return el
}

func (el *ErrorLogger) write(message string) {
	fmt.Println("ERROR Logger: " + message)
}

注意 :在Go的实现中,由于没有传统意义的抽象方法,AbstractLoggerLogMessage 调用 al.write(message)。这意味着嵌入 AbstractLogger 的具体类型必须有一个名为 write 的方法。如果具体类型也想改变 LogMessage 的传递逻辑(例如,处理后就停止),则需要重写 LogMessage 方法。

一个更符合责任链"要么处理要么传递"的Go实现方式,可能是在LogMessage中判断是否能处理,如果不能,则直接调用next.LogMessage。如果能处理,则处理,然后根据策略决定是否继续传递。

修改后的Go AbstractLogger 和具体实现:

go 复制代码
// logger.go (Handler interface - 修正版)
package logging

const (
	LevelInfo  = 1
	LevelDebug = 2
	LevelError = 3
)

type Logger interface {
	SetNext(logger Logger) Logger
	ProcessLog(level int, message string) // 改名为 ProcessLog,更清晰
}

// abstract_logger.go (Base for Concrete Handlers - 修正版)
package logging

// AbstractLogger 基础结构
type AbstractLogger struct {
	level int
	next  Logger
}

func (al *AbstractLogger) SetNext(logger Logger) Logger {
	al.next = logger
	return logger
}

// ProcessLog 模板方法:如果当前级别能处理,则调用 write,否则传递
// 注意:这里的 write 仍然需要具体类型实现。我们将让具体类型重写 ProcessLog。
// 或者,我们可以让 write 成为接口的一部分,但这样每个具体实现都需要检查级别。
// 更常见的做法是,每个具体 Handler 的处理方法自己决定是否处理和是否传递。

// --- 具体实现 (修正版) ---

// info_logger.go
package logging

import "fmt"

type InfoLogger struct {
	level int
	next  Logger
}

func NewInfoLogger(level int) *InfoLogger {
	return &InfoLogger{level: level}
}

func (il *InfoLogger) SetNext(logger Logger) Logger {
	il.next = logger
	return logger
}

func (il *InfoLogger) ProcessLog(level int, message string) {
	if il.level <= level {
		fmt.Println("INFO Logger: " + message)
	}
	if il.next != nil {
		il.next.ProcessLog(level, message)
	}
}

// debug_logger.go
package logging

import "fmt"

type DebugLogger struct {
	level int
	next  Logger
}

func NewDebugLogger(level int) *DebugLogger {
	return &DebugLogger{level: level}
}

func (dl *DebugLogger) SetNext(logger Logger) Logger {
	dl.next = logger
	return logger
}

func (dl *DebugLogger) ProcessLog(level int, message string) {
	if dl.level <= level {
		fmt.Println("DEBUG Logger: " + message)
	}
	if dl.next != nil {
		dl.next.ProcessLog(level, message)
	}
}

// error_logger.go
package logging

import "fmt"

type ErrorLogger struct {
	level int
	next  Logger
}

func NewErrorLogger(level int) *ErrorLogger {
	return &ErrorLogger{level: level}
}

func (el *ErrorLogger) SetNext(logger Logger) Logger {
	el.next = logger
	return logger
}

func (el *ErrorLogger) ProcessLog(level int, message string) {
	if el.level <= level {
		fmt.Println("ERROR Logger: " + message)
	}
	if el.next != nil {
		el.next.ProcessLog(level, message)
	}
}
java 复制代码
// InfoLogger.java (ConcreteHandler)
package com.example.logging;

public class InfoLogger extends Logger {
    public InfoLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("INFO Logger: " + message);
    }
}

// DebugLogger.java (ConcreteHandler)
package com.example.logging;

public class DebugLogger extends Logger {
    public DebugLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("DEBUG Logger: " + message);
    }
}

// ErrorLogger.java (ConcreteHandler)
package com.example.logging;

public class ErrorLogger extends Logger {
    public ErrorLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("ERROR Logger: " + message);
    }
}
客户端使用
go 复制代码
// main.go (示例用法)
/*
package main

import (
	"./logging"
	"fmt"
)

func getChainOfLoggers() logging.Logger {
	errorLogger := logging.NewErrorLogger(logging.LevelError)
	debugLogger := logging.NewDebugLogger(logging.LevelDebug)
	infoLogger := logging.NewInfoLogger(logging.LevelInfo)

	// 构建责任链: Error -> Debug -> Info
	// 意味着 ErrorLogger 是链的开始,如果它处理不了或选择传递,则给 DebugLogger,以此类推
	// 但在这个日志例子中,通常是 Info (最低级) -> Debug -> Error (最高级)
	// 并且每个logger都会打印它能处理的级别以及更高级别的日志

	// 更常见的日志链: Info -> Debug -> Error
	// InfoLogger 处理所有 INFO, DEBUG, ERROR
	// DebugLogger 处理所有 DEBUG, ERROR (如果从 InfoLogger 传递过来)
	// ErrorLogger 处理所有 ERROR (如果从 DebugLogger 传递过来)

	infoLogger.SetNext(debugLogger)
	debugLogger.SetNext(errorLogger)

	return infoLogger // 返回链的头部
}

func main() {
	loggerChain := getChainOfLoggers()

	fmt.Println("--- Sending INFO message ---")
	loggerChain.ProcessLog(logging.LevelInfo, "This is an information.")
	// Expected: INFO, DEBUG, ERROR loggers might print this if their level <= LevelInfo
	// With our current logger logic (this.level <= level), and chain Info -> Debug -> Error:
	// InfoLogger (level 1 <= 1) prints.
	// DebugLogger (level 2 > 1) does not print itself, but passes.
	// ErrorLogger (level 3 > 1) does not print itself.
	// Corrected logic for typical logging: a logger handles messages AT OR ABOVE its configured level.
	// So, if chain is Info(1) -> Debug(2) -> Error(3):
	// LevelInfo message: InfoLogger prints. DebugLogger and ErrorLogger also print.
	// LevelDebug message: InfoLogger does not print. DebugLogger prints. ErrorLogger prints.
	// LevelError message: InfoLogger and DebugLogger do not print. ErrorLogger prints.
	// The example code implements: if my_handler_level <= message_level, then I print.
	// This means a higher level handler (e.g. ErrorLogger with level 3) will print for a lower level message (e.g. Info with level 1).
	// This is typical for logging frameworks where setting a log level (e.g. INFO) means you see INFO and all levels above it (DEBUG, ERROR).

	fmt.Println("\n--- Sending DEBUG message ---")
	loggerChain.ProcessLog(logging.LevelDebug, "This is a debug level information.")
	// Expected (with chain Info(1) -> Debug(2) -> Error(3) and handler_level <= message_level):
	// InfoLogger (1 <= 2) prints.
	// DebugLogger (2 <= 2) prints.
	// ErrorLogger (3 > 2) does not print itself.

	fmt.Println("\n--- Sending ERROR message ---")
	loggerChain.ProcessLog(logging.LevelError, "This is an error information.")
	// Expected (with chain Info(1) -> Debug(2) -> Error(3) and handler_level <= message_level):
	// InfoLogger (1 <= 3) prints.
	// DebugLogger (2 <= 3) prints.
	// ErrorLogger (3 <= 3) prints.

    // Let's clarify the Go logger logic for a more standard CoR where only one handler acts
    // or where a handler acts and then passes. The current Go example is more like a broadcast
    // to all eligible handlers based on level comparison.

    // A more CoR-like logger might be: each logger handles ONLY its specific level.
    // If so, the chain order and logic in ProcessLog would change.
    // Example: if level == il.level { print } else if next != nil { next.ProcessLog }
}
*/
java 复制代码
// Main.java (示例用法)
/*
package com.example;

import com.example.logging.Logger;
import com.example.logging.InfoLogger;
import com.example.logging.DebugLogger;
import com.example.logging.ErrorLogger;

public class Main {

    private static Logger getChainOfLoggers() {
        // 创建不同级别的日志记录器
        Logger errorLogger = new ErrorLogger(Logger.ERROR);
        Logger debugLogger = new DebugLogger(Logger.DEBUG);
        Logger infoLogger = new InfoLogger(Logger.INFO);

        // 构建责任链
        // INFO logger is the first in chain, then DEBUG, then ERROR.
        // A message of a certain level will be handled by loggers whose level is less than or equal to the message's level.
        infoLogger.setNextLogger(debugLogger);
        debugLogger.setNextLogger(errorLogger);

        return infoLogger; // 返回链的头部
    }

    public static void main(String[] args) {
        Logger loggerChain = getChainOfLoggers();

        System.out.println("--- Sending INFO message ---");
        loggerChain.logMessage(Logger.INFO, "This is an information.");
        // Expected output (based on current Java Logger logic):
        // INFO Logger: This is an information.
        // (DEBUG and ERROR loggers will also be called but won't print if their level is higher than INFO)
        // Corrected: With `this.level <= level`, InfoLogger (1<=1) prints. DebugLogger (2>1) no, ErrorLogger (3>1) no.
        // The Java code's logMessage passes to nextLogger REGARDLESS of whether current logger wrote.
        // So, for INFO message (level 1):
        // InfoLogger (level 1): write() called. nextLogger.logMessage(1, ...) called.
        // DebugLogger (level 2): write() NOT called (2 > 1). nextLogger.logMessage(1, ...) called.
        // ErrorLogger (level 3): write() NOT called (3 > 1). nextLogger is null.
        // Output: INFO Logger: This is an information.

        System.out.println("\n--- Sending DEBUG message ---");
        loggerChain.logMessage(Logger.DEBUG, "This is a debug level information.");
        // For DEBUG message (level 2):
        // InfoLogger (level 1): write() called. nextLogger.logMessage(2, ...) called.
        // DebugLogger (level 2): write() called. nextLogger.logMessage(2, ...) called.
        // ErrorLogger (level 3): write() NOT called (3 > 2). nextLogger is null.
        // Output: INFO Logger: This is a debug level information.
        //         DEBUG Logger: This is a debug level information.

        System.out.println("\n--- Sending ERROR message ---");
        loggerChain.logMessage(Logger.ERROR, "This is an error information.");
        // For ERROR message (level 3):
        // InfoLogger (level 1): write() called. nextLogger.logMessage(3, ...) called.
        // DebugLogger (level 2): write() called. nextLogger.logMessage(3, ...) called.
        // ErrorLogger (level 3): write() called. nextLogger is null.
        // Output: INFO Logger: This is an error information.
        //         DEBUG Logger: This is an error information.
        //         ERROR Logger: This is an error information.
    }
}
*/

关于日志示例的说明

上面的日志示例实现了一种"广播式"的责任链,即一个日志消息会被所有级别低于或等于该消息级别的记录器处理。例如,一个 ERROR 消息会被 InfoLoggerDebugLoggerErrorLogger 都记录(如果它们都在链中且按此顺序)。

一个更"纯粹"的责任链变体可能是:

  1. 单一处理:一旦一个处理者处理了请求,请求就不再向下传递。
  2. 条件传递:处理者处理请求后,根据某些条件决定是否继续传递。

例如,在审批流程中,一旦经理批准了小额报销,就不需要再给总监看。这需要在 handleRequest 方法中加入逻辑:如果处理了,则返回或不再调用 successor.handleRequest()

对于日志,当前实现是合理的,因为通常希望高优先级的日志也包含低优先级日志记录器的输出(例如,设置日志级别为DEBUG,则INFO和DEBUG日志都可见)。

7. 总结

责任链模式通过将多个能够处理请求的对象连接成一条链,使得请求可以在链上动态传递,直到被处理为止。它有效地解耦了请求的发送者和接收者,并允许动态地组织和修改处理者链。这种模式在需要多个对象协同处理一个请求,并且具体处理者在运行时确定的场景中非常有用,如审批流程、事件处理、中间件管道等。

记住它的核心:请求沿链传递,逐级处理,解耦收发

相关推荐
qqxhb6 分钟前
零基础设计模式——行为型模式 - 迭代器模式
java·设计模式·go·迭代器模式
琢磨先生David20 分钟前
Java 24 字符串模板:重构字符串处理的技术革新
java·开发语言
有梦想的攻城狮1 小时前
spring中的ImportSelector接口详解
java·后端·spring·接口·importselector
晨曦~~1 小时前
SpringCloudAlibaba和SpringBoot版本问题
java·spring boot·后端
天天进步20151 小时前
Java应用性能监控与调优:从JProfiler到Prometheus的工具链构建
java·开发语言·prometheus
武昌库里写JAVA2 小时前
iview组件库:关于分页组件的使用与注意点
java·vue.js·spring boot·学习·课程设计
小伍_Five2 小时前
spark数据处理练习题番外篇【上】
java·大数据·spark·scala
海尔源码2 小时前
支持多语言的开源 Web 应用
java
摩天崖FuJunWANG2 小时前
c语言中的hashmap
java·c语言·哈希算法
LUCIAZZZ2 小时前
Java设计模式基础问答
java·开发语言·jvm·spring boot·spring·设计模式