当前位置: 首页 > article >正文

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

前言

在现代软件工程中,设计模式是解决常见问题的有效工具之一。它们吸收了前人的经验,不仅帮助开发者编写更清晰、更可维护的代码,还能促进团队之间的沟通和协作。责任链模式(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.kler.cn/news/356020.html

相关文章:

  • 重写QObjiet虚函数timerEvent()启动定时器
  • ssm基于SSM框架的成绩管理系统的设计与实现+vue
  • 矩阵相关算法
  • 【基于数据分析-画图展示】
  • 【Unity - 屏幕截图】技术要点
  • Spring Boot框架的电影评论系统设计与实现
  • 多种方式实现安全帽佩戴检测
  • 上位机-简介
  • 与双指针的亲密接触:快与慢的浪漫交错
  • 细说网络安全五家龙头企业,你去过哪一家?
  • Linux 中,`tee` 和 `grep` 分别用于处理输出流[从标准输入读取数据,并将数据同时输出到标准输出和文件]和文本内容的过滤。
  • fmql之Linux Uart
  • 【C++刷题】力扣-#252-会议室
  • 【AI学习】Mamba学习(八):HiPPO通用框架定义和方法
  • JAVA学习-练习试用Java实现“选择排序”
  • 学生社会适应能力测试
  • 【最新华为OD机试E卷-支持在线评测】内存资源分配(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)
  • WordPress+Nginx 安装教程
  • 越狱你的 iPhone 安全吗?
  • JMeter之mqtt-jmeter 插件介绍