初识设计模式 - 职责链模式

时间:2022-11-08 09:07:58

简介

职责链设计模式(Chain Of Responsibility Design Pattern)的定义是,将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。

同时,将这些接收对象串成一条链,并沿着这条链传递这个对象,直至链上的某个接收对象能够处理这个请求为止。

职责链可以是一条直线、一个环或一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求。

典型实现

首先,定义一个抽象处理者 Handler 类,其代码示例如下:

public abstract class Handler {
    // 维持对下一个处理者的引用
    protected Handler successor;

    public void setHandler(Handler successor) {
        this.successor = successor;
    }

    public abstract void handleRequest(String Request);
}

然后,定义一个具体处理者 ConcreteHandler 子类,其代码示例如下:

public class ConcreteHandler extends Handler {
    public void handleRequest(String request) {
        // 处理请求或者转发请求
        this.successor.handleRequest(request);
        // 执行完当前处理方法后,可以执行下一个处理者的处理,完成链路循环
    }
}

对于客户端而言,只需要知道第一个具体处理者是谁即可,无需关心后续的其他处理者。这就像是操作链表一样,知道链表的头结点即可访问链表的所有结点。

分类

根据处理者对象的行为,职责链模式可以分为纯的职责链模式和不纯的职责链模式。

纯的职责链模式

一个纯的职责链模式要求一个具体处理者对象只能在两种行为中选择一个:要么承担全部责任,要么将责任推给下家。

同时,纯的职责链模式要求一个请求必须被某一个具体处理者对象所接受,不能出现某个请求未被处理者对象接收的情形。

不纯的职责链模式

不纯的职责链模式是与纯的职责链模式相对的一种模式。

在一个不纯的职责链模式中,允许某个请求被具体处理者部分处理后还能向下传递,或者一个具体处理者处理完某个请求后其后继处理者可以继续处理该对象,而且同一个请求可以最终不被任何处理者对象所接收。

总结

优点

职责链模式的主要优点如下:

  • 将发送者和接收者解耦,客户端无需知道请求被哪一个对象处理
  • 当工作流程发生变化,可以动态地改变链内的成员或调动它们的次序,也可动态的新增或删除职责
  • 通过链式结构串联处理者,可以根据需要增加新的处理类,符合开闭原则
  • 纯的职责链模式明确了各类的职责范围,符合类的单一职责原则

缺点

职责链模式的主要缺点如下:

  • 由于请求没有一个明确的处理者,不能保证请求一定会被处理
  • 对于较长的职责链,请求的处理涉及到多个处理对象,系统性能将受到一定影响
  • 职责链的建立要靠客户端来保证,增加了客户端的复杂性,建链不当可能造成循环引用

适用场景

职责链模式的适用场景如下:

  • 多个对象可以处理一个请求,但具体由哪一个对象处理在运行时自动确定
  • 需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求
  • 动态地指定一组处理者,或者改变链中处理者之间的次序

源码

在 JDK 中 java.util.logging.Logger 记录日志有可能有多个不同的 Handler 处理器,如果使用这些 Handler 处理器就是一种职责链模式的运用。

如下是源码部分:

public void log(LogRecord record) {
    if (!isLoggable(record.getLevel())) {
        return;
    }
    Filter theFilter = config.filter;
    if (theFilter != null && !theFilter.isLoggable(record)) {
        return;
    }

    Logger logger = this;
    while (logger != null) {
        final Handler[] loggerHandlers = isSystemLogger
            ? logger.accessCheckedHandlers()
            : logger.getHandlers();

        for (Handler handler : loggerHandlers) {
            handler.publish(record);
        }

        final boolean useParentHdls = isSystemLogger
            ? logger.config.useParentHandlers
            : logger.getUseParentHandlers();

        if (!useParentHdls) {
            break;
        }

        logger = isSystemLogger ? logger.parent : logger.getParent();
    }
}