责任链模式下,解决开闭原则问题实践

news2024/11/27 15:28:05

前言

在现代软件工程中,设计模式是解决常见问题的有效工具之一。它们吸收了前人的经验,不仅帮助开发者编写更清晰、更可维护的代码,还能促进团队之间的沟通和协作。责任链模式(Chain of Responsibility Pattern)作为一种常用的设计模式,广泛应用于多种场景,尤其适用于处理需要经过多个处理步骤的请求或命令。本文将从概念到具体实现,让你深刻理解责任链设计模式。

一、什么是责任链设计模式

责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着一个处理链传递,直到链中的某个对象处理它。这样,发送者无需知道哪个对象将处理请求,所有的处理对象都可以尝试处理请求或将请求传递给链上的下一个对象。总结来说,责任链模式实质上是一组链式调用的逻辑。

image-20241019114647704

在代码开发和维护过程中,随着系统复杂性的增加,原有的代码结构可能会变得难以维护。责任链模式正是为了解决这些问题而提出的。当代码中出现以下情形时,采用责任链设计模式进行重构便显得尤为重要:

  • 职责单一:责任链模式可以将每个验证逻辑封装到一个独立的处理器中,每个处理器负责单一的验证职责,符合单一职责原则。
  • 可扩展性:增加新的验证逻辑时,只需添加新的处理器,而不需要修改现有的代码。
  • 清晰的流程:将所有验证逻辑组织在一起,使得代码结构更加清晰,易于理解。

通过责任链模式,我们可以构建一个更加模块化、易于维护和扩展的系统架构。接下来,我们将详细介绍责任链模式的应用场景、优缺点以及具体的实现方法。

二、Java代码举例实现

现在我们采用Java实现一个过滤器调用的实现。总流程如下:

image-20241019120243192

  1. 定义过滤器接口和请求/响应类
package com.example.provider.pattern.filter;

/**
 * Filter 接口定义了过滤器的基本行为。
 * 每个具体的过滤器都需要实现此接口,并提供自己的 doFilter 实现。
 */
public interface Filter {
    /**
     * 执行过滤操作。
     *
     * @param request  当前请求对象
     * @param response 当前响应对象
     * @param chain    过滤链,用于调用链中的下一个过滤器
     */
    void doFilter(Request request, Response response, FilterChain chain);
}

/**
 * Response 类表示一个响应对象。
 * 包含与响应相关的属性和方法。
 */
class Response {
    // 响应相关属性和方法
}

/**
 * Request 类表示一个请求对象。
 * 包含与请求相关的属性和方法。
 */
class Request {
    // 请求相关属性和方法
}

/**
 * FilterChain 类表示一个过滤器链。
 * 它负责管理过滤器的顺序,并允许调用链中的下一个过滤器。
 */
class FilterChain {
    // 过滤器链的相关属性和方法
    
    /**
     * 调用链中的下一个过滤器。
     */
    public void doFilter() {
        // 实现调用链中的下一个过滤器的逻辑
    }
}

具体过滤器实现:

package com.example.provider.pattern.filter;

public class ConcreteFilterA implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain chain) {
        // 执行过滤操作A
        System.out.println("ConcreteFilterA 执行过滤");
        // 继续传递请求
        chain.doFilter(request, response);
    }
}

public class ConcreteFilterB implements Filter {
    @Override
    public void doFilter(Request request, Response response, FilterChain chain) {
        // 执行过滤操作B
        System.out.println("ConcreteFilterB 执行过滤");
        // 继续传递请求
        chain.doFilter(request, response);
    }
}
  1. 执行过滤方法流程:
package com.example.provider.pattern.filter;

import java.util.ArrayList;
import java.util.List;

/**
 * 过滤器链,用于依次执行添加到链中的过滤器。
 */
public class FilterChain {
    private List<Filter> filters = new ArrayList<>();
    private int index = 0;

    public void addFilter(Filter filter) {
        filters.add(filter);
    }

    public void doFilter(Request request, Response response) {
        if (index < filters.size()) {
            Filter filter = filters.get(index++);
            filter.doFilter(request, response, this);
        }
    }
}

  1. Client端调用职责链方法
package com.example.provider.pattern.filter;

public class Client {
    public static void main(String[] args) {
        // 创建过滤器
        Filter filterA = new ConcreteFilterA();
        Filter filterB = new ConcreteFilterB();

        System.out.println("创建过滤器链");
        // 创建过滤器链并添加过滤器
        FilterChain filterChain = new FilterChain();
        filterChain.addFilter(filterA);
        filterChain.addFilter(filterB);

        // 创建请求和响应对象
        Request request = new Request();
        Response response = new Response();

        // 通过过滤器链处理请求
        filterChain.doFilter(request, response);
        System.out.println("过滤器链创建完毕");
    }
}
  1. 执行结果如下:
创建过滤器链
ConcreteFilterA 执行过滤
ConcreteFilterB 执行过滤
过滤器链创建完毕

通过前面的例子,我们可以看到,在手动实现责任链模式时,最大的问题在于 Client 类中需要手动添加过滤器。这种方式不仅增加了代码的复杂性,还不符合开闭原则(Open/Closed Principle, OCP),即软件实体应当对扩展开放,对修改关闭。

三、Spring环境解决开闭原则问题

为了解决这一问题,我们可以利用Spring上下文(ApplicationContext)来管理和获取Bean实例的

我们下面通过创建博客的例子,来在Spring环境下解决这个开闭原则问题。先说说它的具体执行流程:

image-20241019120440030

先来了解一下项目结构:

image-20241019131954509

这里的话,我们只给出核心类代码,具体代码可见:https://gitee.com/madaoEE/blog-chain

  1. 职责链接口:
public interface BlogCreateChainHandler <T> extends Ordered {

    /**
     * 执行责任链逻辑
     *
     * @param requestParam 责任链执行入参
     */
    void handler(T requestParam);

    /**
     * @return 责任链组件标识
     */
    String mark();
}
  1. 职责链接口实现类
package com.pxl.demo.service.handler;

import com.pxl.demo.service.chain.BlogCreateChainHandler;
import org.springframework.stereotype.Component;

/**
 * @author MADAO
 * @create 2024 - 10 - 19 13:00
 */
@Component
public class BlogCreateNotNullChainFilter implements BlogCreateChainHandler {
    @Override
    public void handler(Object requestParam) {
        System.out.println("博客创建参数非空判断");
    }

    @Override
    public String mark() {
        return "blogCreate";
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

其他省略…

  1. 解决开闭原则核心类——Spring上下文
package com.pxl.demo.service.chain;

import org.springframework.beans.BeansException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;

@Component
public final class MerchantAdminChainContext<T> implements ApplicationContextAware, CommandLineRunner {

    /**
     * 应用上下文,我们这里通过 Spring IOC 获取 Bean 实例
     */
    private ApplicationContext applicationContext;
    private final Map<String, List<BlogCreateChainHandler>> abstractChainHandlerContainer = new HashMap<>();

    /**
     * 责任链组件执行
     *
     * @param mark         责任链组件标识
     * @param requestParam 请求参数
     */
    public void handler(String mark, T requestParam) {
        // 根据 mark 标识从责任链容器中获取一组责任链实现 Bean 集合
        List<BlogCreateChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);
        if (CollectionUtils.isEmpty(abstractChainHandlers)) {
            throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
        }
        abstractChainHandlers.forEach(each -> each.handler(requestParam));
    }

    @Override
    public void run(String... args) throws Exception {
        // 从 Spring IOC 容器中获取指定接口 Spring Bean 集合
        Map<String, BlogCreateChainHandler> chainFilterMap = applicationContext.getBeansOfType(BlogCreateChainHandler.class);
        chainFilterMap.forEach((beanName, bean) -> {
            // 判断 Mark 是否已经存在抽象责任链容器中,如果已经存在直接向集合新增;如果不存在,创建 Mark 和对应的集合
            List<BlogCreateChainHandler> abstractChainHandlers = abstractChainHandlerContainer.getOrDefault(bean.mark(), new ArrayList<>());
            abstractChainHandlers.add(bean);
            abstractChainHandlerContainer.put(bean.mark(), abstractChainHandlers);
        });
        abstractChainHandlerContainer.forEach((mark, unsortedChainHandlers) -> {
            // 对每个 Mark 对应的责任链实现类集合进行排序,优先级小的在前
            unsortedChainHandlers.sort(Comparator.comparing(Ordered::getOrder));
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
  1. 具体Service执行
package com.pxl.demo.service.impl;

import com.pxl.demo.service.BlogService;
import com.pxl.demo.service.chain.MerchantAdminChainContext;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * @author MADAO
 * @create 2024 - 10 - 19 12:58
 */
@Service
@RequiredArgsConstructor
public class BlogServiceImpl implements BlogService {

    private final MerchantAdminChainContext merchantAdminChainContext;
    @Override
    public String addBlog() {
        merchantAdminChainContext.handler("blogCreate",null);
        return "创建成功";
    }
}

调用接口,打印日志如下:http://localhost:8080/blog/add

博客创建参数非空判断
博客创建其他判断
博客创建审核判断

四、总结

本文通过详细的理论介绍和Java代码示例,展示了如何使用责任链设计模式来构建一个模块化的系统。责任链模式通过将请求沿处理链传递,允许系统内部以一种松耦合的方式处理请求,提高了系统的可扩展性和可维护性。同时,通过Spring框架的依赖注入机制,我们解决了传统责任链实现中不符合开闭原则的问题,使得添加新的处理逻辑变得更加简单和灵活。

然而,值得注意的是,并非所有情况都适合应用责任链模式。在选择是否使用该模式时,我们需要考虑实际需求和场景特点。例如,在请求处理流程固定不变或者处理步骤较少的情况下,直接编码可能更为简洁有效。设计模式是一个工具,合理地根据实际情况选用合适的模式才是关键。对于责任链模式而言,它最适合于处理那些具有多层次决策逻辑的需求场景,能够有效地简化代码结构,提高系统的灵活性。

如果这篇文章对你有帮助,请点赞告诉我,这将是我继续分享的动力!感谢你的支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2219319.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言 | Leetcode C语言题解之第485题最大连续1的个数

题目&#xff1a; 题解&#xff1a; int findMaxConsecutiveOnes(int* nums, int numsSize) {int maxCount 0, count 0;for (int i 0; i < numsSize; i) {if (nums[i] 1) {count;} else {maxCount fmax(maxCount, count);count 0;}}maxCount fmax(maxCount, count);…

最近网站频繁跳转到黑产网站,怀疑是51.la统计代码的问题

最近我的几个网站&#xff0c;都出现了一个问题&#xff0c;就是访问的时候会莫名其妙的跳转到黑产网站。 通过排查了网页代码&#xff0c;发现网页都有一个共同点&#xff0c;就是使用了51.la统计。为什么会怀疑是51la统计代码问题&#xff1f;因为我的网页只有统计代码外没有…

Vulnhub打靶-jangow

基本信息 靶机下载&#xff1a;https://www.vulnhub.com/entry/jangow-101,754/ 攻击机器&#xff1a;192.168.20.128&#xff08;Windows操作系统&#xff09;& 192.168.20.138&#xff08;kali&#xff09; 提示信息&#xff1a;这个框的秘密是枚举&#xff01; 靶机…

汽车票预订系统:SpringBoot框架的优势

6系统测试 6.1概念和意义 测试的定义&#xff1a;程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为&#xff1a; 目的&#xff1a;发现程序的错误&#xff1b; 任务&#xff1a;通过在计算机上执行程序&#xff0c;暴露程序中潜在的错误。 另一个…

软考(网工)——网络安全

文章目录 &#x1f550;网络安全基础1️⃣网络安全威胁类型2️⃣网络攻击类型 &#x1f551;现代加密技术1️⃣私钥密码/对称密码体制2️⃣对称加密算法总结3️⃣公钥密码/非对称密码4️⃣混合密码5️⃣国产加密算法 - SM 系列6️⃣认证7️⃣基于公钥的认证 &#x1f552;Hash …

证件照小程序源码,前后端稳定运行

演示&#xff1a;证寸照制作 运行环境: Linux Nginx PHP >5.6 MySQL>5.6 安装步骤: 1.下载源码上传至你的服务器宝塔面板 2.直接添加站点选择源码目录&#xff0c;新建数据库 3.设置代码执行目录为/web 4.在浏览器中输入你的域名&#xff0c;会提示安装&#xff0c;填写…

Flink消费Kafka实时写入Doris

本文模拟实际生产环境&#xff0c;通过FileBeat采集日志信息到Kafka&#xff0c;再通过Flink消费Kafka实时写入Doris。 文章目录 Filebeat采集日志到KafkaFlink消费Kafka实时写入Doris总结 Filebeat采集日志到Kafka 常见的日志采集工具有以下几种&#xff1a;Flume、Logstash和…

自动机器学习(AutoML)

utoML是PAI的提供的自动寻找超参组合的机器学习增强型服务。您在训练模型时&#xff0c;如果超参组合复杂度过高&#xff0c;需大量训练资源和手工调试工作&#xff0c;可以使用AutoML来节省模型调参时间&#xff0c;提升模型调优效率和模型质量。 基础概念 超参数&#xff1a;…

Spring 获取URL中的参数

PathVariable 获取多个变量参数重命名 获取 URL 中的 Id&#xff0c;可以根据 Id 到数据库中筛选相应的内容 Id 的类型是可以定义的&#xff0c;这里定义为 Integer 类型 并且在 RequestMapping中需要定义路径 {articleId} PathVariable 从路径中获取 变量 获取多个变量 参数…

【软件运行类文档】项目试运行方案,试运行计划书(word原件)

一、 试运行目的 &#xff08;一&#xff09; 系统功能、性能与稳定性考核 &#xff08;二&#xff09; 系统在各种环境和工况条件下的工作稳定性和可靠性 &#xff08;三&#xff09; 检验系统实际应用效果和应用功能的完善 &#xff08;四&#xff09; 健全系统运行管理体制&…

jmeter发送post请求

在jmeter中&#xff0c;有两种常用的请求方式&#xff0c;get和post.它们两者的区别在于get请求的参数一般是放在路径中&#xff0c;可以使用用户自定义变量和函数助手等方式进行参数化&#xff0c;而post请求的参数不能随url发送&#xff0c;而是作为请求体提交给服务器。而在…

打开游戏提示丢失(或找不到)XINPUT1_3.DLL的多种解决办法

xinput1_3.dll是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;它在Windows操作系统中扮演着重要的角色。该文件作为系统库文件&#xff0c;通常存放于C:\Windows\System32目录下&#xff08;对于32位系统&#xff09;或C:\Windows\SysWOW64目录下&#xff08;对于…

PPT自动化:快速更换PPT图片(如何保留原图片样式等参数更换图片)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 PPT更换图片 📒1. 安装 `python-pptx` 模块2. 加载PPT文件3. 查找并替换图片3.1 查找图片形状3.2 获取原图片的样式和位置3.3 替换图片4. 保存修改后的PPT文件5. 设置图片的相关参数5.1 设置透明度5.2 设置边框🚀 保留所有参…

FFmpeg 4.3 音视频-多路H265监控录放C++开发四 :RGB颜色

一 RGB 的意义&#xff1f; 为什么要从RGB 开始讲起呢&#xff1f; 因为最终传输到显卡显示器的颜色都是RGB 即使能处理YUV的API&#xff0c;本质上也是帮你做了从 YUV 到 RGB的转换。 RGB888 表示 R 占8bit&#xff0c;G 占8bit&#xff0c;B 占8bit&#xff0c;也就是每一…

内网微隔离,三步防横移——基于微隔离的横移攻击防护方案

引言 在网络攻防的战场上&#xff0c;横移攻击如同隐匿的刺客&#xff0c;一旦突破边界&#xff0c;便在内网肆意游走&#xff0c;给企业网络安全带来致命损害。当前多数企业数据中心内网如同未设防的城池&#xff0c;面对突破外围边界的“刺客”毫无招架之力。蔷薇灵动基于多…

Thread类的介绍

线程是操作系统中的概念&#xff0c;操作系统中的内核实现了线程这种机制&#xff0c;同时&#xff0c;操作系统也提供了一些关于线程的API让程序员来创建和使用线程。 在JAVA中&#xff0c;Thread类就可以被视为是对操作系统中提供一些关于线程的API的的进一步的封装。 多线…

基于SpringBoot+Vue+uniapp微信小程序的澡堂预订的微信小程序的详细设计和实现

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

Goland 搭建Gin脚手架

一、使用编辑器goland 搭建gin 打开编辑器 新建项目后 点击 create 二、获得Gin框架的代码 命令行安装 go get -u github.com/gin-gonic/gin 如果安装不上&#xff0c;配置一下环境 下载完成 官网git上下载 这样就下载完成了。、 不过这种方法需要设置一下GOPATH 然后再执…

Electron-(三)网页报错处理与请求监听

在前端开发中&#xff0c;Electron 是一个强大的框架&#xff0c;它允许我们使用 Web 技术构建跨平台的桌面应用程序。在开发过程中&#xff0c;及时处理网页报错和监听请求是非常重要的环节。本文将详细介绍 Electron 中网页报错的日志记录、webContents 的监听事件以及如何监…

【uniapp】打包成H5并发布

目录 1、设置配置mainifest.sjon 1.1 页面标题 1.2 路由模式 1.3 运行的基础路径 2、打包 2.1 打包入口 2.2 打包成功 2.3 依据目录找到web目录 3、 将web目录整体拷贝出来 4、上传 4.1 登录uniapp官网注册免费空间 4.2 上传拷贝的目录 4.3 检查上传是否正确 5、…