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

前言

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

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

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

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

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

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

二、Java代码举例实现

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

  1. 定义过滤器接口和请求/响应类
csharp 复制代码
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() {
        // 实现调用链中的下一个过滤器的逻辑
    }
}

具体过滤器实现:

vbscript 复制代码
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. 执行过滤方法流程:
java 复制代码
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端调用职责链方法
java 复制代码
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环境下解决这个开闭原则问题。先说说它的具体执行流程:

先来了解一下项目结构:

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

  1. 职责链接口:
csharp 复制代码
public interface BlogCreateChainHandler <T> extends Ordered {
​
    /**
     * 执行责任链逻辑
     *
     * @param requestParam 责任链执行入参
     */
    void handler(T requestParam);
​
    /**
     * @return 责任链组件标识
     */
    String mark();
}
  1. 职责链接口实现类
typescript 复制代码
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上下文
java 复制代码
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执行
java 复制代码
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框架的依赖注入机制,我们解决了传统责任链实现中不符合开闭原则的问题,使得添加新的处理逻辑变得更加简单和灵活。

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

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

相关推荐
弗拉唐4 分钟前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi7736 分钟前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀1 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20201 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深1 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
shuangrenlong2 小时前
slice介绍slice查看器
java·ubuntu
牧竹子2 小时前
对原jar包解压后修改原class文件后重新打包为jar
java·jar
数据小爬虫@2 小时前
如何利用java爬虫获得淘宝商品评论
java·开发语言·爬虫