设计模式-16章(责任链模式)


第十六章 责任链模式

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,用于将请求的发送者和接收者解耦,并允许多个对象依次处理请求。在这个模式中,请求沿着一个链传递,直到某个对象能够处理它。责任链模式通常用于避免请求发送者与接收者之间的直接耦合,从而提高系统的可扩展性和灵活性。

以下是责任链模式的关键要点和详细说明:

  1. 主要角色:

    • Handler(处理者):定义处理请求的接口,并维护一个指向下一个处理者的引用。通常包含一个处理请求的方法。
    • ConcreteHandler(具体处理者):实现Handler接口,具体处理请求的类。如果能够处理请求,就进行处理,否则将请求传递给下一个处理者。
  2. 责任链的组织:责任链通常以链表、树状结构或其他方式组织。请求从链的头部开始传递,逐个经过每个处理者,直到找到一个能够处理请求的对象,或者到达链的末尾。

  3. 请求处理过程:当请求进入责任链时,从链头开始,每个处理者判断自己是否能够处理该请求。如果可以处理,它会执行相应的操作;如果不能处理,它将请求传递给下一个处理者,直到请求被处理或者到达链的末尾。

  4. 优点:

    • 解耦:责任链模式将请求发送者和接收者解耦,发送者无需知道是哪个接收者处理请求。
    • 灵活性:可以很容易地添加或移除处理者,以满足不同的需求。
    • 可维护性:每个处理者专注于处理特定的任务,使代码更容易维护和测试。
  5. 缺点:

    • 可能导致请求无法被处理:如果责任链没有被正确配置,或者没有终止条件,请求可能会传递到链的末尾而无法被处理。
    • 性能问题:在长链中,性能可能受到影响,因为请求需要经过多次的处理者判断。

责任链模式在实际应用中有多种场景,例如:

  • 请求审批流程:不同级别的管理者可以构成责任链,请求依次通过不同管理者审批。
  • 日志记录:责任链可以用于按照不同级别记录日志,如错误日志、警告日志和信息日志。
  • 数据验证:数据验证可以由一系列验证器构成责任链,每个验证器验证数据的一部分。

以下是一个简单的Java代码示例,演示了责任链模式的实现。我们将创建一个日志记录的责任链,根据日志级别不同,将日志消息记录到不同的目标。

首先,我们定义处理请求的接口 Logger 和具体的处理者类 ConsoleLoggerFileLogger 以及 EmailLogger

// Logger 接口
public abstract class Logger {
    //日志级别
    protected int level;
    //下一个接收对象
    protected Logger nextLogger;

    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }

    public void logMessage(int level, String message) {
        if (this.level <= level) {
            writeMessage(message);
        }
        //传递给下一个接收对象
        if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }

    protected abstract void writeMessage(String message);
}

// ConsoleLogger 具体处理者
public class ConsoleLogger extends Logger {
    public ConsoleLogger(int level) {
        this.level = level;
    }

    protected void writeMessage(String message) {
        System.out.println("Console Logger: " + message);
    }
}

// FileLogger 具体处理者
public class FileLogger extends Logger {
    public FileLogger(int level) {
        this.level = level;
    }

    protected void writeMessage(String message) {
        System.out.println("File Logger: " + message);
    }
}

// EmailLogger 具体处理者
public class EmailLogger extends Logger {
    public EmailLogger(int level) {
        this.level = level;
    }

    protected void writeMessage(String message) {
        System.out.println("Email Logger: " + message);
    }
}

接下来,我们将创建一个客户端类来测试责任链模式:

public class ChainOfResponsibilityDemo {
    public static void main(String[] args) {
        Logger consoleLogger = new ConsoleLogger(Logger.INFO);
        Logger fileLogger = new FileLogger(Logger.DEBUG);
        Logger emailLogger = new EmailLogger(Logger.ERROR);

        consoleLogger.setNextLogger(fileLogger);
        fileLogger.setNextLogger(emailLogger);

        consoleLogger.logMessage(Logger.INFO, "This is an informational message.");
        consoleLogger.logMessage(Logger.DEBUG, "This is a debug message.");
        consoleLogger.logMessage(Logger.ERROR, "This is an error message.");
    }
}

在这个示例中,我们首先创建了三个具体的处理者(ConsoleLoggerFileLoggerEmailLogger),每个处理不同级别的日志消息。然后,我们将它们连接成责任链,并使用客户端类来测试不同级别的日志消息。

输出将会是:

Console Logger: This is an informational message.
Console Logger: This is a debug message.
Console Logger: This is a debug message.
File Logger: This is a debug message.
Console Logger: This is an error message.
File Logger: This is an error message.
Email Logger: This is an error message.

这个示例演示了责任链模式如何将请求沿着链传递,直到找到能够处理请求的处理者。根据不同的日志级别,消息被记录到不同的目标中。

1. 职责链模式的原理和实现

职责链模式的英文翻译是 Chain Of Responsibility Design Pattern。在 GoF 的《设计模式》中,它是这么定义的:

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.

翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。

这么说比较抽象,我用更加容易理解的话来进一步解读一下。

在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。

关于职责链模式,我们先来看看它的代码实现。结合代码实现,你会更容易理解它的定义。职责链模式有多种实现方式,我们这里介绍两种比较常用的。

第一种实现方式如下所示。其中,Handler 是所有处理器类的抽象父类,handle() 是抽象方法。每个具体的处理器类(HandlerA、HandlerB)的 handle() 函数的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能处理,则交由后面的处理器来处理(也就是调用 successor.handle())。HandlerChain 是处理器链,从数据结构的角度来看,它就是一个记录了链头、链尾的链表。其中,记录链尾是为了方便添加处理器。

public abstract class Handler {
    protected Handler successor = null;
    //设置继承者
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    
    public abstract void handle();
}

具体的处理者:

public class HandlerA extends Handler {
    
    @Override
    public boolean handle() {
        boolean handled = false;
        //...
        if (!handled && successor != null) {
            successor.handle();
        }
    }
}

public class HandlerB extends Handler {
    @Override
    public void handle() {
        boolean handled = false;
        //...
        if (!handled && successor != null) {
            successor.handle();
        }
    }
}

处理器链:

public class HandlerChain {
    private Handler head = null;
    private Handler tail = null;
    
    public void addHandler(Handler handler) {
        handler.setSuccessor(null);
        if (head == null) {
            head = handler;
            tail = handler;
            return;
        }
        tail.setSuccessor(handler);
        tail = handler;
    }
    
    public void handle() {
        if (head != null) {
            head.handle();
        }
    }
}

使用举例:

public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

其实上面的实现就是链表的实现

实际上,上面的代码实现不够优雅。处理器类的 handle() 函数,不仅包含自己的业务逻辑,还包含对下一个处理器的调用,也就是代码中的 successor.handle()。一个不熟悉这种代码结构的程序员,在添加新的处理器类的时候,很有可能忘记在 handle() 函数中调用successor.handle(),这就会导致代码出现 bug。

针对这个问题,我们对代码进行重构,利用模板模式,将调用 successor.handle() 的逻辑从具体的处理器类中剥离出来,放到抽象父类中。这样具体的处理器类只需要实现自己的业务逻辑就可以了。重构之后的代码如下所示:

public abstract class Handler {
    protected Handler successor = null;
    
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    
    public final void handle() {
        boolean handled = doHandle();
        if (successor != null && !handled) {
            successor.handle();
        }
    }
    //自己只需要重写这个方法即可
    protected abstract boolean doHandle();
}
public class HandlerA extends Handler {
    @Override
    protected boolean doHandle() {
        boolean handled = false;
        //...
        return handled;
    }
}

public class HandlerB extends Handler {
    @Override
    protected boolean doHandle() {
        boolean handled = false;
        //...
        return handled;
    }
}
// HandlerChain和Application代码不变

我们再来看第二种实现方式,代码如下所示。这种实现方式更加简单。HandlerChain 类用数组非链表来保存所有的处理器,并且需要在 HandlerChain 的 handle() 函数中,依次调用每个处理器的 handle() 函数。

public interface IHandler {
    boolean handle();
}
public class HandlerA implements IHandler {
    @Override
    public boolean handle() {
        boolean handled = false;
        //...
        return handled;
    }
}

public class HandlerB implements IHandler {
    @Override
    public boolean handle() {
        boolean handled = false;
        //...
        return handled;
    }
}
public class HandlerChain {
    private List<IHandler> handlers = new ArrayList<>();
    public void addHandler(IHandler handler) {
        this.handlers.add(handler);
    }
    
    public void handle() {
        for (IHandler handler : handlers) {
            boolean handled = handler.handle();
            if (handled) {
                break;
            }
        }
    }
}

使用举例:

public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

在 GoF 给出的定义中,如果处理器链上的某个处理器能够处理这个请求,那就不会继续往下传递请求。实际上,职责链模式还有一种变体,那就是请求会被所有的处理器都处理一遍,不存在中途终止的情况。

2. 职责链模式的应用场景举例

职责链模式的原理和实现讲完了,我们再通过一个实际的例子,来学习一下职责链模式的应用场景。

对于支持 UGC(User Generated Content,用户生成内容)的应用(比如论坛)来说,用户生成的内容(比如,在论坛中发表的帖子)可能会包含一些敏感词(比如涉黄、广告、反动等词汇)。针对这个应用场景,我们就可以利用职责链模式来过滤这些敏感词。

对于包含敏感词的内容,我们有两种处理方式,一种是直接禁止发布,另一种是给敏感词打马赛克(比如,用 *** 替换敏感词)之后再发布。第一种处理方式符合 GoF 给出的职责链模式的定义,第二种处理方式是职责链模式的变体。

我们这里只给出第一种实现方式的代码示例,如下所示,并且,我们只给出了代码实现的骨架,具体的敏感词过滤算法并没有给出:

public interface SensitiveWordFilter {
    boolean doFilter(Content content);
}
public class SexyWordFilter implements SensitiveWordFilter {
    @Override
    public boolean doFilter(Content content) {
        boolean legal = true;
        //...
        return legal;
    }
}
// PoliticalWordFilter、AdsWordFilter类代码结构与SexyWordFilter类似
public class SensitiveWordFilterChain {
    private List<SensitiveWordFilter> filters = new ArrayList<>();
    
    public void addFilter(SensitiveWordFilter filter) {
        this.filters.add(filter);
    }
    // return true if content doesn't contain sensitive words.
    public boolean filter(Content content) {
        for (SensitiveWordFilter filter : filters) {
            if (!filter.doFilter(content)) {
                return false;
            }
        }
        return true;
    }
}
public class ApplicationDemo {
    public static void main(String[] args) {
        SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
        filterChain.addFilter(new AdsWordFilter());
        filterChain.addFilter(new SexyWordFilter());
        filterChain.addFilter(new PoliticalWordFilter());
        boolean legal = filterChain.filter(new Content());
        if (!legal) {
            // 不发表
        } else {
            // 发表
        }
    }
}

看了上面的实现,你可能会说,我像下面这样也可以实现敏感词过滤功能,而且代码更加简单,为什么非要使用职责链模式呢?这是不是过度设计呢?

public class SensitiveWordFilter {
    // return true if content doesn't contain sensitive words.
    public boolean filter(Content content) {
        if (!filterSexyWord(content)) {
            return false;
        }
        if (!filterAdsWord(content)) {
            return false;
        }
        if (!filterPoliticalWord(content)) {
            return false;
        }
        return true;
    }
    
    private boolean filterSexyWord(Content content) {
        //....
    }
    private boolean filterAdsWord(Content content) {
        //...
    }
    private boolean filterPoliticalWord(Content content) {
        //...
    }
}

我们前面多次讲过,应用设计模式主要是为了应对代码的复杂性,让其满足开闭原则,提高代码的扩展性。这里应用职责链模式也不例外。实际上,我们在讲解策略模式的时候,也讲过类似的问题,比如,为什么要用策略模式?当时的给出的理由,与现在应用职责链模式的理由,几乎是一样的,你可以结合着当时的讲解一块来看下。

2.1 首先,我们来看,职责链模式如何应对代码的复杂性。

将大块代码逻辑拆分成函数,将大类拆分成小类,是应对代码复杂性的常用方法。应用职责链模式,我们把各个敏感词过滤函数继续拆分出来,设计成独立的类,进一步简化了SensitiveWordFilter 类,让 SensitiveWordFilter 类的代码不会过多,过复杂。

2.2 其次,我们再来看,职责链模式如何让代码满足开闭原则,提高代码的扩展性。

当我们要扩展新的过滤算法的时候,比如,我们还需要过滤特殊符号,按照非职责链模式的代码实现方式,我们需要修改 SensitiveWordFilter 的代码,违反开闭原则。不过,这样的修改还算比较集中,也是可以接受的。

而职责链模式的实现方式更加优雅,只需要新添加一个 Filter 类,并且通过 addFilter() 函数将它添加到 FilterChain 中即可,其他代码完全不需要修改。不过,你可能会说,即便使用职责链模式来实现,当添加新的过滤算法的时候,还是要修改客户端代码(ApplicationDemo),这样做也没有完全符合开闭原则。

实际上,细化一下的话,我们可以把上面的代码分成两类:框架代码和客户端代码。其中,ApplicationDemo 属于客户端代码,也就是使用框架的代码。除 ApplicationDemo 之外的代码属于敏感词过滤框架代码。

假设敏感词过滤框架并不是我们开发维护的,而是我们引入的一个第三方框架,我们要扩展一个新的过滤算法,不可能直接去修改框架的源码。这个时候,利用职责链模式就能达到开篇所说的,在不修改框架源码的情况下,基于职责链模式提供的扩展点,来扩展新的功能。换句话说,我们在框架这个代码范围内实现了开闭原则。

除此之外,利用职责链模式相对于不用职责链的实现方式,还有一个好处,那就是配置过滤算法更加灵活,可以只选择使用某几个过滤算法。


文章作者: 念心卓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 念心卓 !
  目录