责任链设计及各种变体,彻底搞懂责任链

2023年 10月 16日 78.7k 0

设计模式是对代码的一种规范,并不局限于语言。他的主题思想主要是:单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、迪米特原则、接口隔离原则

责任链概念图

  • 其中按照功能分为三大功能: 创建型模式、结构型模式、行为型模式。而我们今天的主角就是属于行为型模式的责任链模式。

具体场景

  • 还记得上次离职是什么时候吗?离职的流程麻烦吗?各种的手续各种的审批搞的自己是焦头烂额。
  • 试想想为什么他们不能自动扭转呢?为什么我不能提交申请,剩下他们自己内部开始扭转?今天我就要用责任链模式来实现一个离职申请流程。

UML 结构

  • 首先,我对原始的责任链做了一个简单的修改。个人喜欢面向接口开发。所以我这里多了一个 StandoutHandler 接口。这个接口里规定了存在哪些方法。

StandoutHandler

  • 我们只需要暴露出来对下一个节点的设置和获取。除此之外我们只需要再暴露出来一个执行方法即可。
public interface StandoutHandler {
    /**
     * 获取责任链上下一个执行器
     * @return
     */
    public StandoutHandler getNextHandler();
    public void setNextHandler(StandoutHandler handler);

    public void execute(Request request);
}

AbstractHandler

  • 在 execute 方法中算是父类方法中,为什么这里我还要在抽离出一个 run 方法呢?只有这样 execute 才能起到通用的作用,至于 run 交给每个子类实现,这样方便我们对通用型的逻辑进行处理,比如现在我想对每个子类的动作进行记录,那么我可以在 execute 方法中进行拦截,这里的 execute 起到一个 AOP 的作用。
  • 我们的 execute 方法逻辑也很简单,先执行自己的业务,执行结束后放行到下一节点。
public abstract class AbstractHandler implements StandoutHandler{
    private StandoutHandler nextHandler;

    @Override
    public StandoutHandler getNextHandler() {
        return nextHandler;
    }

    @Override
    public void setNextHandler(StandoutHandler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract void run(Request request);

    @Override
    public void execute(Request request) {
        run(request);
        if (this.nextHandler != null) {
            this.nextHandler.execute(request);
        }
    }
}

DefaultConcrete

  • 为了避免无法预料的异常情况,所以这里我内置了一个具体实现类放置在责任链的顶端。
public class DefaultConcreteHandler extends AbstractHandler {
    @Override
    public void run(Request request) {
        System.out.println(String.format("我是%s审批...接下来我将自动交给%s处理",this.getClass().getSimpleName(),getNextHandler().getClass().getSimpleName()));
    }
}

  • 这里为了演示效果,我在每个具体实现类中都是简单的打印内容。你可能会说为什么不把他放到 execute 方法上?我这里纯粹是为了掩饰在 run 中的业务操作。

  • 所以其他 4 个具体实现类,我这里就不贴代码了,读者自己可以修改类名即可。

Client

  • 接口、抽象、具体实现都已经准备好了,剩下的我们还缺一个对责任链的组装功能。这里 1 个 Client 我们可以组装出一个链条,从而对应我们一套业务。
public class Client {
    private static Client client = new Client();
    private StandoutHandler standoutHandler = new DefaultConcreteHandler();
    private StandoutHandler lastStandoutHandler = standoutHandler;

    public static Client getInstance() {
        return client;
    }
    public StandoutHandler registerHandler(StandoutHandler handler) {
        this.lastStandoutHandler.setNextHandler(handler);
        this.lastStandoutHandler = handler;
        return handler;
    }
    public StandoutHandler build() {
        return standoutHandler;
    }
}

  • 这里我简单的使用了数据结构中链表的思想来组装出这个链条。有了链条下面我们就可以直接使用了。

Test

public class Test {
    public static void main(String[] args) {
        //init chains
        Client.getInstance().registerHandler(new ConcreteHandler1());
        Client.getInstance().registerHandler(new ConcreteHandler2());
        Client.getInstance().registerHandler(new ConcreteHandler3());
        Client.getInstance().registerHandler(new ConcreteHandler4());
        StandoutHandler standoutHandler = Client.getInstance().build();
        standoutHandler.execute(new Request());
    }
}

效果

  • 接下来我们看看上面的测试出现的效果吧。

  • 这里我们可以看到,在请求发起者(Test) 只需要通过 Client 获取到责任链并将请求数据传递给它,剩下的就无需关注了,内部的流程完全是责任链自己处理。我们可以清楚的看到流程从 DefaultConcrete -> ConcreteHandler1 -> ConcreteHandler2 -> ConcreteHandler3 -> ConcreteHandler4

  • 在子类(run 方法)的业务逻辑中完全是独立的,他们之间完全隔离唯一共同点就是他们拥有同个参数传递对象。

优势在哪

  • 上面我们从理论到实践介绍了责任链模式。那么他的优势在哪里?为什么要使用责任链?

  • 责任链模式下请求的发起者而言可以说是: 衣来伸手、饭来张口。发起者完全充当一个透明人的角色,内部的逻辑不需要了解,也没必要了解,最终只要一个结果。

责任链变形之双向链

  • 我们一直都知道源码中处处都是设计模式的体现,每当我熟悉设计模式之后准备去源码里挑战一下,结果一看发现跟我了解到的设计模式不太一样,看着看着就把自己绕进去了。
  • 其实这并不是说明源码中不遵从设计模式,而是在设计模式的基础上会做适当的变换。就比如我们针对上面的责任链模式稍作变形就符合了数据库连接这个场景了。

幻想需求

  • 我们都知道数据库连接涉及到连接和释放两个动作。此时我有这样一个需求,上面提到的 4 个具体实现类上分别需要创建不同的数据库连接进行操作,每次操作完都需要对资源进行释放,我这里还是那句话,完全是为了模拟出场景从而体现设计模式的价值所在。

  • 通过上面的图示我们也能感觉出来,责任链感觉由单链表变成的双链表了。我们可以将连接和释放抽象成两个方法,链表内部通过两个指针进行双向绑定,这样就能从头至尾、从尾至头执行,话不多说我们开始上代码。

StandoutHandler

public interface StandoutHandler {
    /**
     * 获取责任链上下一个执行器
     * @return
     */
    public StandoutHandler getNextHandler();
    public void setNextHandler(StandoutHandler handler);

    /**
     * 获取责任链上下一个执行器
     * @return
     */
    public StandoutHandler getPreHandler();
    public void setPreHandler(StandoutHandler handler);

    public void connect(Request request);
    public void close(Request request);
}

AbstractHandler

public abstract class AbstractHandler implements StandoutHandler{
    private StandoutHandler nextHandler;
    private StandoutHandler preHandler;

    @Override
    public StandoutHandler getNextHandler() {
        return nextHandler;
    }

    @Override
    public void setNextHandler(StandoutHandler nextHandler) {
        this.nextHandler = nextHandler;
    }
    @Override
    public StandoutHandler getPreHandler() {
        return preHandler;
    }

    @Override
    public void setPreHandler(StandoutHandler nextHandler) {
        this.preHandler = nextHandler;
    }

    public abstract void runConnect(Request request);
    public abstract void runClose(Request request);

    @Override
    public void connect(Request request) {
        runConnect(request);
        if (this.nextHandler != null) {
            this.nextHandler.connect(request);
        }
    }
    @Override
    public void close(Request request) {
        runClose(request);
        if (this.preHandler != null) {
            this.preHandler.close(request);
        }
    }
}

DefaultConcrete

public class DefaultConcreteHandler extends AbstractHandler {
    @Override
    public void runConnect(Request request) {
        System.out.println(String.format("我是%s正在开启连接...接下来我将自动交给%s处理",this.getClass().getSimpleName(),getNextHandler().getClass().getSimpleName()));
    }
    @Override
    public void runClose(Request request) {
        System.out.println(String.format("我是%s正在关闭连接...接下来我将自动交给%s处理",this.getClass().getSimpleName(),getNextHandler().getClass().getSimpleName()));
    }
}

Client

public class Client {
    private static Client client = new Client();
    private StandoutHandler standoutHandler = new DefaultConcreteHandler();
    private StandoutHandler lastStandoutHandler = standoutHandler;

    public static Client getInstance() {
        return client;
    }
    public StandoutHandler registerHandler(StandoutHandler handler) {
        handler.setPreHandler(this.lastStandoutHandler);
        this.lastStandoutHandler.setNextHandler(handler);
        this.lastStandoutHandler = handler;
        return handler;
    }
    public StandoutHandler build() {
        return standoutHandler;
    }
    public StandoutHandler hail() {
        return lastStandoutHandler;
    }
}

Test

public class Test {
    public static void main(String[] args) {
        //init chains
        Client.getInstance().registerHandler(new ConcreteHandler1());
        Client.getInstance().registerHandler(new ConcreteHandler2());
        Client.getInstance().registerHandler(new ConcreteHandler3());
        Client.getInstance().registerHandler(new ConcreteHandler4());
        StandoutHandler standoutHandler = Client.getInstance().build();
        standoutHandler.connect(new Request());
        StandoutHandler hail = Client.getInstance().hail();
        hail.close(new Request());
    }
}

效果

  • 这样我们通过双向链表实现了如何控制责任链的方向

责任链变形之环形链

  • 环形的责任链使用场景是啥呢?这点主要是方便 Client 组装而言的,试想一下我们现在想做一个促销打折的功能,每个实现类对应一种打折活动,且内部我们的打折是不会出现重叠的情况的,比如 ConcreteHandler1 对应消费金额介于 100~200 则减 10,ConcreteHandler2 在消费金额介于 300-350 则减 15 ,以此类推。那么对于这种责任节点其实并没有什么顺序而言,对于一次输入每次有且仅有可能一个节点满足条件,而且实现类过多没必要在乎他的顺序,那么这个时候就需要我们使用圆形责任链模式了。
  • 环形链在双向链的基础上,我们只需要解决头尾两个节点,只需要将他们两个连接起来即可以实现环形链了。

注意事项

  • 环形责任链会有个致命的问题,那就是死循环

  • 所以为了避免死循环,我们在环形链中必须加入一个计数器。这个计数器的功能就是记录节点被执行的次数,在环形上的节点我们得保证每个节点执行的次数必须有一个上限值,这里我选择 1,即表示每个节点最多只能执行 1 次,关于这个计数器我们就可以借助 AbstractHandler 中抽离的父方法中进行拦截控制
public abstract class AbstractHandler implements StandoutHandler{
    private StandoutHandler nextHandler;
    private StandoutHandler preHandler;
    private int connectTimes = 0;
    private int closeTimes = 0;
    @Override
    public StandoutHandler getNextHandler() {
        return nextHandler;
    }

    @Override
    public void setNextHandler(StandoutHandler nextHandler) {
        this.nextHandler = nextHandler;
    }
    @Override
    public StandoutHandler getPreHandler() {
        return preHandler;
    }

    @Override
    public void setPreHandler(StandoutHandler nextHandler) {
        this.preHandler = nextHandler;
    }

    public abstract void runConnect(Request request);
    public abstract void runClose(Request request);

    @Override
    public void connect(Request request) {
        connectTimes++;
        if (connectTimes > 1) {
            return;
        }
        runConnect(request);
        if (this.nextHandler != null) {
            this.nextHandler.connect(request);
        }
    }
    @Override
    public void close(Request request) {
        closeTimes++;
        if (closeTimes > 1) {
            return;
        }
        runClose(request);
        if (this.preHandler != null) {
            this.preHandler.close(request);
        }
    }
}

责任链变形之通用责任链

  • 上面我们变形出来双向链、环形链都是从链条本身出发的,既然是发散思维我们就尽可能去发散,Client 方进行变形就会得倒各种各样组合,除此之外我们仔细观察下 UML 图示

  • 我们貌似忽略了指责链中最基本的一个对象那就是请求对象本身,假如我们现在针对订单存在一个责任链,针对用户对象又存在一个责任链。

  • 订单责任链、用户责任链实际上都是一个链表,那么他们区别点在哪呢?对就是责任链上每个节点处理的对象即我们之前谈及的 Request ,那么该如何实现通用责任链呢?其实很简单,我们只需要将方法中 Request 对象改为 Object 即可。

  • 因为改成通用责任链,那么我们在组装责任链的时候就需要注意将一类的节点组装到一起。

  • 为了避免组装这不清楚责任节点的功能,我们让每个节点提供它支持的范围,这样即使你将用户节点组装到了订单责任链上也没业务上的问题,顶多就是浪费一次循环机会。

责任链总结

  • 设计模式只是我们前辈们对代码的一种思想规范,我们要学习他们的思想但不能局限在设计模式下,要懂得结合实际业务进行适当的变通才真真正的王道!!!,比如我们理论知识只能学习到责任链模式实际上就是一个链表,来解决业务发起和业务实现解耦。
  • 单链表就是责任链模式的核心思想,但是我们不能一成不变一只单链表走天下,举一反三既然单链表是核心,那么我们就可以实现已双链表为核心的责任链模式,比如我们的连接释放场景。
  • 除了单链表、双链表我们还可以变形出环形链表的责任链。

白嫖区

HandlerInterceptor:SpringBoot拦截器的基本使用(详解)_springboot之handlerinterceptor拦截器的使用详解_Java Punk的博客-CSDN博客

Spring Boot 实现登录拦截器(最强实战版) - Java技术栈 - 博客园

责任链模式在 Spring 中的应用 - 掘金

责任链设计模式(职责链模式)

责任链模式 | 菜鸟教程

java - 一起学设计模式 - 责任链模式 - 个人文章 - SegmentFault 思否

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论