设计模式 - 责任链

一、前言

​ 相信大家平时或多或少都间接接触过责任链设计模式,只是可能有些同学自己不知道此处用的是该设计模式,比如说 Java Web 中的 Filter 过滤器,就是非常经典的责任链设计模式的例子。

那么什么是责任链设计模式呢?

客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

​ 技术领域的相关定义总是那样的晦涩难懂,因此不理解不重要,下面将会用例子来帮助理解。

责任链模式有哪些优点,能解决什么问题,我们为什么要使用它呢?

优点:

  • 将请求与处理解耦。
  • 请求处理对象只需关注自己需要处理的请求进行处理即可,对于不需要自己处理的请求,直接转发给下一个处理对象即可,符合单一职责原则。
  • 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。
  • 链路结构灵活,可以通过改变链路结构动态的新增或者删除处理对象。即易于拓展新的请求处理类,符合开闭原则。

缺点:

  • 会存在责任链太长,而大多数处理者都不会对请求进行处理的情况,导致走完整个责任链的时间太长,影响整体性能。
  • 如果责任链配置不完善,会存在处理对象循环引用,从而造成死循环,导致系统崩溃的情况。

适用场景:

  • 多条件流程判断,如权限控制
  • ERP 系统流程审批
  • Java Web 过滤器的底层实现 Filter
  • Mybatis 中的分页插件 PageHelper

二、代码示例

1. 导包信息

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

2. 代码结构

3. 具体代码

流程扩展类(主要记录每个流程的上一个处理对象和下一个处理对象的信息) ProcessDTO.java:

java 复制代码
package com.dxc.responsibility.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 流程扩展类
 *
 * @Author xincheng.du
 * @Date 2023/8/30 14:26
 */
@Data
@AllArgsConstructor
public class ProcessDTO {

    /**
     * 流程处理器id
     */
    private Integer handlerId;

    /**
     * 全限定名
     */
    private String fullName;

    /**
     * 上一个流程处理器id
     */
    private Integer preHandlerId;

    /**
     * 下一个流程处理器id
     */
    private Integer nextHandlerId;

}

流程链枚举 ProcessChainEnum.java

java 复制代码
package com.dxc.responsibility.enums;

import com.dxc.responsibility.dto.ProcessDTO;
import lombok.AllArgsConstructor;

/**
 * 流程链枚举
 * 此处的枚举类可以换成数据库配置,当然你也可以理解为数据库表中的一条条数据
 * 这样就可以根据更改枚举类或数据库中的顺序,来更改实际处理流程中的执行顺序了
 *
 * @Author xincheng.du
 * @Date 2023/8/30 14:26
 */
@AllArgsConstructor
public enum ProcessChainEnum {

    /**
     * 流程链中的第一个流程
     */
    FIRST(new ProcessDTO(1, "com.dxc.responsibility.handler.impl.FirstProcessHandler", null, 2)),

    /**
     * 流程链中的第二个流程
     */
    SECOND(new ProcessDTO(2, "com.dxc.responsibility.handler.impl.SecondProcessHandler", 1, 3)),

    /**
     * 流程链中的第三个流程
     */
    THIRD(new ProcessDTO(3, "com.dxc.responsibility.handler.impl.ThirdProcessHandler", 2, null)),
    ;

    ProcessDTO processDTO;

    public ProcessDTO getProcessDTO() {
        return processDTO;
    }

}

流程处理器工厂(主要用来初始化流程链,并返回第一个流程处理器) ProcessHandlerFactory.java

java 复制代码
package com.dxc.responsibility.factory;

import cn.hutool.core.util.ReflectUtil;
import com.dxc.responsibility.dto.ProcessDTO;
import com.dxc.responsibility.handler.AbstractProcessHandler;
import com.dxc.responsibility.handler.impl.FirstProcessHandler;
import com.dxc.responsibility.service.ProcessService;
import com.dxc.responsibility.service.impl.ProcessServiceImpl;
import lombok.extern.slf4j.Slf4j;

/**
 * 流程处理器工厂
 *
 * @Author xincheng.du
 * @Date 2023/8/30 14:26
 */
@Slf4j
public class ProcessHandlerFactory {

    private ProcessHandlerFactory() {}

    private static final ProcessService processService = new ProcessServiceImpl();

    /**
     * 初始化流程链,并返回流程链中第一个流程处理器
     *
     * @return  {@link FirstProcessHandler}
     */
    public static FirstProcessHandler getFirstProcessHandler() {
        // 获取第一个流程扩展类
        ProcessDTO firstProcessDTO = processService.getFirstProcessDTO();
        // 根据流程扩展类获取第一个流程处理器
        AbstractProcessHandler firstProcessHandler = newProcessHandler(firstProcessDTO);
        ProcessDTO tempProcessDTO = firstProcessDTO;
        Integer nextHandlerId;
        AbstractProcessHandler tempProcessHandler = firstProcessHandler;
        // 迭代遍历所有handler,以及将它们链接起来
        while ((nextHandlerId = tempProcessDTO.getNextHandlerId()) != null) {
            // 根据处理器id获取流程扩展类
            ProcessDTO processDTO = processService.getProcessEntity(nextHandlerId);
            // 根据流程扩展类获取具体的流程处理器
            AbstractProcessHandler processHandler = newProcessHandler(processDTO);
            assert tempProcessHandler != null;
            tempProcessHandler.setNext(processHandler);
            tempProcessHandler = processHandler;
            tempProcessDTO = processDTO;
        }
        // 返回第一个handler
        return (FirstProcessHandler) firstProcessHandler;
    }



    /**
     * 根据流程扩展类获取具体的流程处理器
     *
     * @param dto   流程扩展类
     * @return  {@link AbstractProcessHandler}
     */
    private static AbstractProcessHandler newProcessHandler(ProcessDTO dto) {
        // 获取全限定类名
        String className = dto.getFullName();
        try {
            // 根据全限定类名,加载并初始化该类
            Class<?> clazz = Class.forName(className);
            return (AbstractProcessHandler) ReflectUtil.newInstance(clazz);
        } catch (ClassNotFoundException e) {
            log.error("根据流程扩展类获取流程处理器失败,原因:{},流程处理器:{}", e.getMessage(), dto.getFullName());
            e.printStackTrace();
        }
        return null;
    }

}

抽象流程处理器(主要是用来定义每个具体处理的方法模板,不做具体逻辑处理,具体的流程处理器需要集成当前抽象类,并实现各自的流程处理逻辑) AbstractProcessHandler.java

java 复制代码
package com.dxc.responsibility.handler;

/**
 * 流程处理抽象类
 *
 * @Author xincheng.du
 * @Date 2023/8/31 11:25
 */
public abstract class AbstractProcessHandler {

    /**
     * 下一关用当前抽象类来接收
     */
    protected AbstractProcessHandler next;

    public void setNext(AbstractProcessHandler next) {
        this.next = next;
    }

    /**
     * 具体处理逻辑
     * 需要子类实现
     */
    public abstract void process();

}

第一个流程处理器(此处模拟具体的流程处理对象,具体的处理逻辑写在此处) FirstProcessHandler.java

java 复制代码
package com.dxc.responsibility.handler.impl;

import com.dxc.responsibility.handler.AbstractProcessHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * 第一个流程处理器
 *
 * @Author xincheng.du
 * @Date 2023/8/30 11:25
 */
@Slf4j
public class FirstProcessHandler extends AbstractProcessHandler {

    @Override
    public void process() {
        log.info("第一个流程处理开始对请求进行处理......");
        if (this.next != null) {
            this.next.process();
        }
    }

}

第二个流程处理器(此处模拟具体的流程处理对象,具体的处理逻辑写在此处) SecondProcessHandler.java

java 复制代码
package com.dxc.responsibility.handler.impl;

import com.dxc.responsibility.handler.AbstractProcessHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * 第二个流程处理器
 *
 * @Author xincheng.du
 * @Date 2023/8/30 11:25
 */
@Slf4j
public class SecondProcessHandler extends AbstractProcessHandler {

    @Override
    public void process() {
        log.info("第二个流程处理开始对请求进行处理......");
        if (this.next != null) {
            this.next.process();
        }
    }

}

第三个流程处理器(此处模拟具体的流程处理对象,具体的处理逻辑写在此处) SecondProcessHandler.java

java 复制代码
package com.dxc.responsibility.handler.impl;

import com.dxc.responsibility.handler.AbstractProcessHandler;
import lombok.extern.slf4j.Slf4j;

/**
 * 第三个流程处理器
 *
 * @Author xincheng.du
 * @Date 2023/8/30 11:25
 */
@Slf4j
public class ThirdProcessHandler extends AbstractProcessHandler {

    @Override
    public void process() {
        log.info("第三个流程处理开始对请求进行处理......");
        if (this.next != null) {
            this.next.process();
        }
    }

}

流程扩展类接口(主要对流程扩展类进行封装初始化,为流程处理器工厂提供方法) ProcessService.java

java 复制代码
package com.dxc.responsibility.service;

import com.dxc.responsibility.dto.ProcessDTO;

/**
 * 流程扩展类 接口
 *
 * @Author xincheng.du
 * @Date 2023/8/30 14:26
 */
public interface ProcessService {

    /**
     * 根据流程处理器id获取流程扩展类
     *
     * @param handlerId 流程处理器id
     * @return  {@link ProcessDTO}
     */
    ProcessDTO getProcessEntity(Integer handlerId);

    /**
     * 获取第一个流程扩展类
     *
     * @return {@link ProcessDTO}
     */
    ProcessDTO getFirstProcessDTO();

}

流程扩展类接口的具体实现 ProcessServiceImpl.java

java 复制代码
package com.dxc.responsibility.service.impl;

import com.dxc.responsibility.dto.ProcessDTO;
import com.dxc.responsibility.enums.ProcessChainEnum;
import com.dxc.responsibility.service.ProcessService;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

/**
 * 流程扩展类 逻辑处理
 *
 * @Author xincheng.du
 * @Date 2023/8/30 14:26
 */
@Slf4j
public class ProcessServiceImpl implements ProcessService {

    /**
     * 流程扩展类Map
     * key=流程处理器id value=流程扩展类 ProcessDTO
     */
    private static Map<Integer, ProcessDTO> processDTOMap = new HashMap<>();

    /**
     * 流程链初始化
     * 将枚举中配置的handler初始化到map中,方便获取
     */
    static {
        ProcessChainEnum[] values = ProcessChainEnum.values();
        for (ProcessChainEnum value : values) {
            ProcessDTO processDTO = value.getProcessDTO();
            processDTOMap.put(processDTO.getHandlerId(), processDTO);
        }
    }

    @Override
    public ProcessDTO getProcessEntity(Integer handlerId) {
        return processDTOMap.get(handlerId);
    }

    @Override
    public ProcessDTO getFirstProcessDTO() {
        for (Map.Entry<Integer, ProcessDTO> entry : processDTOMap.entrySet()) {
            ProcessDTO value = entry.getValue();
            //  没有上一个handler的就是第一个
            if (value.getPreHandlerId() == null) {
                return value;
            }
        }
        log.error("获取第一个流程扩展类出错");
        return null;
    }
}

客户端(相当于请求发起者) ProcessClient.java

java 复制代码
package com.dxc.responsibility;

import com.dxc.responsibility.factory.ProcessHandlerFactory;
import com.dxc.responsibility.handler.AbstractProcessHandler;

/**
 * 客户端
 *
 * @Author xincheng.du
 * @Date 2023/8/30 14:26
 */
public class ProcessClient {

    public static void main(String[] args) {
        // 获取第一个流程处理器
        AbstractProcessHandler firstProcessHandler = ProcessHandlerFactory.getFirstProcessHandler();
        assert firstProcessHandler != null;
        // 执行第一个流程处理器
        firstProcessHandler.process();
    }

}

4. 运行结果

相关推荐
hummhumm1 分钟前
Oracle 第29章:Oracle数据库未来展望
java·开发语言·数据库·python·sql·oracle·database
wainyz11 分钟前
Java NIO操作
java·开发语言·nio
工业3D_大熊16 分钟前
【虚拟仿真】CEETRON SDK在船舶流体与结构仿真中的应用解读
java·python·科技·信息可视化·c#·制造·虚拟现实
lzb_kkk25 分钟前
【JavaEE】JUC的常见类
java·开发语言·java-ee
爬山算法1 小时前
Maven(28)如何使用Maven进行依赖解析?
java·maven
编程、小哥哥1 小时前
设计模式之抽象工厂模式(替换Redis双集群升级,代理类抽象场景)
redis·设计模式·抽象工厂模式
2401_857439691 小时前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧6661 小时前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索1 小时前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨1 小时前
Filter和Listener
java·filter