【设计模式】责任链模式的设计与示例

news2024/11/23 12:04:04

前言

责任链模式是一种行为设计模式,执行上它允许请求沿着一条处理链路依次向下传递,每个处理节点都能对当前状态的请求进行处理,满足一定条件后传递给下一个处理节点,亦或者直接结束这一次处理流程。
请添加图片描述
在现实生产环境中,需要用到责任链模式的场景还是很多的,比如:

  • 多层条件准入控制,如人事审批流程、权限检验、游戏通关判断等
  • 多环节拦截处理,如 Java 过滤器 Filter、组装生产链路等

在这些场景里,使用责任链模式的优势在于,当其中的某个环节需要进行新增、移除、修改时,可以只对单个节点进行操作,不会影响其他节点的执行过程,保证了整个责任链的稳定,代码更加容易维护和迭代。

模型结构设计

责任链模式初步设计 - 基础属性定义

在上述提到的应用场景中,多层条件准入控制涉及到条件判断,满足某些条件后可以直接中断链路流程,跳出服务;多环节拦截处理通常是一个全链路处理,历经所有环节后到达终点才算结束这一流程的操作。在模型设计上,前者也只是增加了一个对中间结果的判断操作,在整体结构上基本是一致的。基于这样的思路,可以给出责任链模式的初步设计。
请添加图片描述

这里为了结构的通用化,定义了一个上下文实体 ContextDO,里面可以根据实际的业务需求在内部定义成员变量,在整理责任链处理流程中,相关的上下文数据则保留在这个实体中并传递下去。

  • Interface 接口 Handler 定义了两个基本方法
    • handle:执行当前节点的行为
    • setNextHandler:指定当前节点的下一个责任节点
  • AbstractHandler: 为抽象类,内部定义了一个可继承的成员变量 next,用来记录当前节点的下一个责任节点。在该抽象类中,可以实现通用的处理流程。
  • HandlerOneHandlerTwo:具体的实现类,继承了抽象类 AbstractHandler,可根据具体需求重写 handler 方法。
  • Client:业务执行客户端,提供入口获取可执行的责任链并启动其执行流程。

责任链模式进一步设计 - 抽象通用方法

在上面责任链设计模式模型的基础上,各个具体实现类一般会有相同的 handle 动作,这个时候可以将这个执行的内容统一封装到抽象类 AbstractHandler 里面。同时,还可以新增一个方法 isComplete,判断执行动作后的状态是否已满足条件,从而决定从此结束责任链流程或者决定进入下一个处理节点。
请添加图片描述

如图,相同的执行动作被统一抽象封装到抽象类 AbstractHandler 的handle 方法中,另外内部单独封装出来一个 action 方法,供实现类对具体执行动作进行重写。同样新增的方法还有 preActionpostACtionisComplete 等。如果某个具体的 Handler 实现类有特殊的处理动作,则可以在实现类中再重写这些方法。

  • preAction:预处理(可选)。每个处理节点在执行处理流程前,可以对上下文数据进行一些预处理,如对前面已处理的对象进行去重、过滤等。
  • postAction:后置处理(可选)。执行处理流程后,对结果进行后置处理,如将当前流程处理的对象加入去重表,用以后续过滤等操作。
  • isComplete:完成条件判断。判断当前节点执行完成后,是否完成全链路目标,是则结束流程,否则继续下一个节点的动作。亦可以调换判断逻辑。

以下是一个简单的抽象类实现 demo:

@Data
public class ContextDO {
    /** 执行条件,可在 preAction 里更新 */
    private List<Object> conditions;
    
    /** 返回结果,每次执行 action 后新增 */
    private List<Object> resultList;
    
	/** 已处理对象,可在 postAction 里更新 */
    private Set<Object> existedObjSet;
    
    /** 目标请求数量 */
    private Integer querySize;
}

public abstract class AbstractHandler implements Handler {
	/** 下一个处理器节点 */
    protected Handler next;
    
    @Override
    public void handle(ContextDO context) {
        try {
            preAction(context);
            action(context);
            postAction(context);            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        /** 未达到完成目标,且还有下一个处理节点,则继续推进责任链任务 */
        if (!isComplete(context) && this.next != null) {
            this.next.handle(context);
        }
    }
    
    @Override
    public abstract void preAction(ContextDO context);
    
    @Override
    public void postAction(ContextDO context) {
        context.getExistedObjSet().addAll(context.getConditions);
    }
    
    @Override
    public void action(ContextDO context) {
        // do something and add new result into #context.resultList.
    }
    
    @Override
    public boolean isComplete(ContextDO context) {
        return context.getResultList() != null && context.getResultList().size() >= context.getQuerySize();
    }
    
    @Override
    public void setNextHandler(Handler next) {
        this.next = next;
    }
}

在 demo 中定义了一个示例的上下文实体 ContextDO,其字段分别可在 preActionactionpostACtionisComplete 中用到。根据业务的具体情况,可调整上下文需要的字段,或者重写抽象类中封装的各个方法。

责任链模式服务的调用

前面已经完成了责任链模式的设计,从代码中可以看出,责任链任务执行的入口就是首个处理节点的 handle 方法,调用该方法后,整个流程就能结合 isComplete 方法和指向下个处理节点的 next 推动任务的进行。下面介绍几种启动责任链服务的方式,也就是业务客户端的实现方式。

简单客户端 - 按需组合

正如上面 UML 设计图所示,我们可以定义一个 Client 类,直接手动拼接需要组成的责任链节点,直接执行即可。

public class Client {
    public Object process(Object input) {
        ContextDO context = new ContextDO();
        // do something. 将 input 的内容封装到 context 中
        
        AbstractHandler handler1 = new HandlerOne();
        AbstractHandler handler2 = new HandlerTwo();
        AbstractHandler handler3 = new HandlerThree();
        AbstractHandler handler4 = new HandlerFour();

        handler1.setNextHandler(handler2);
        handler2.setNextHandler(handler3);
        handler3.setNextHandler(handler4);
        
        return handler1.hander(context);
    }
}

这种方式的优点在于代码可读性强,责任链的组合比较灵活,能否满足不同业务的定制化需求;而缺点在于,每一次调用服务的时候,都需要重新创建对象,完成后则立即释放资源,如果请求较多,则会一直重复这个操作,白白损耗了性能。因此,当责任链的组成相对固定时,我们完全可以定义一个稳定的、全局可引用的责任链,这里就可以结合工厂模式的思想了。

责任链工厂模式 - 统一装配

责任链工厂模式本质上与上面按需组合的简单客户端实现是相同的,只不过更多的结合 Spring 的一些特性,如自动装配、服务代理等,使代码更加整洁,更具拓展性和可维护性。

这里采用自动装配加上 @Order 注解来组装责任链,通过责任链工厂提供统一的责任链服务。

@Order(1)
@Service
public class HandlerOne extends AbstractHandler {
    // ...
}

@Order(2)
@Service
public class HandlerTwo extends AbstractHandler {
    // ...
}

责任链工厂中实现对责任链各节点的组装,并提供一个统一的管理器,供业务方直接启用服务。

@Service
public class ChainFactory {
    
    private AbstractHandler headHandler;
    
    @Resource
    private List<AbstractHandler> handlerList;
    
    @PostConstruct
    public void init() {
        handlerList.sort(AnnotationAwareOrderComparator.INSTANCE);
        int size = handlerList.size();
        for (int i = 0; i < size - 1; i++) {
            handlerList.get(i).setNextHandler(handlerList.get(i + 1));
        }
        headHandler = handlerList.get(0);
    }
    
    public AbstractHandler getHeadHandler() {
        return this.handHandler;
    }
}

客户端可以通过以上工厂直接调用方法 ChainFactory.getHeadHandler() 获取到责任链头节点,随后执行 handle 方法即可启用相关服务了。

责任链模式更多拓展 - 工厂策略模式

上述的 demo 代码将继承了抽象类 AbstractHandler 的所有子类都加载到列表后,并根据每个实现类 @Order 注解的值进行排序,组装成责任链,将头节点通过 getter 方法对外开放。如果业务出现更多不同业务场景的责任链,或者同样的业务场景里,需要提供不同的组合方案,则可以在上述基础上做进一步拓展。

假如需要支持更多不一样的场景,可以粗暴地去实现新的抽象类,在新的抽象类中支持新的业务场景,对应的也需要有新的责任链工厂对外开放接口。

假如是同样的业务场景需要支持不同的节点组合方案,即在同一个工厂内需要提供不同的责任链,此时可以在工厂中使用策略模式。每一种策略对应一种责任链组合方案,在责任链工厂中通过一个映射表来维护所有的责任链方案。而不同责任链方案的初始化,可以通过配置文件进行定义,这样的可读性、可维护性最高,灵活度也最高。这里就不再提供具体的实现 demo 了。

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

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

相关文章

工作流框架研究

工作流框架研究 主流开源框架介绍OsWorkFlowJBPMActivitiFlowableCamundaCamunda 和Flowable对比功能上对比性能上对比 总结 主流开源框架介绍 OsWorkFlow 对于比较简单的流程&#xff0c;OsWorkFlow会是一个比较好的选择&#xff0c;对于复杂的流程就不推荐了&#xff0c;Os…

小 C 的数学(math)

祝大家劳动节快乐&#xff01;&#xff01;小手动起来 言归正传┏ (゜ω゜)☞ 题目描述 小 C 想要成为一名 OIer&#xff0c;于是他提前学习数学&#xff0c;为 OI 做好铺垫。这一天&#xff0c;他的数学老师给了一道题&#xff1a;给定正整数 a&#xff0c;以及给定一个区间 …

Python基础之列表元组

1.列表 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 去锁定它的位置&#xff0c;或索引&#xff0c;第一个索引是0&#xff0c;第二个索引是1&#xff0c;依此类推。Python有6个序列的内置类型&#xff0c;但最常见的是列表和元组。序列都可以进行的操作…

HTB-Forge

HTB-Forge 信息收集80端口 立足user -> root 信息收集 80端口 试试上传图片看看有什么限制。 jpg上传成功&#xff0c;并且会给一个随机的文件名存储图片&#xff0c;过了一阵子图片就会被清除。 上传phpinfo后访问界面出现报错。 看来没有执行上传的PHP代码&#xff0…

第43天-DP-第九章 ● 139.单词拆分 ● 关于多重背包,你该了解这些! ● 背包问题总结篇!

文章目录 1. 单词拆分2.多重背包3. 背包总结 1. 单词拆分 s class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());// 1. dp[i] 为true代表 可以拆分为一个或者多…

PySyft框架

openmined社区开源的pysyft框架可以提供安全的联邦学习&#xff0c;有助于解决基于“不可见数据”的统计分析与建模开发。在PySyft中&#xff0c;syft是重要的张量&#xff0c;通过建立SyftTensor抽象类来表现张量链的运算或数据状态转换。如图5-7所示&#xff0c;张量链的结构…

3.3 泰勒公式例题分析

例1 写出函数f(x)带有拉格朗日余项的n阶麦克劳林公式 我的答案&#xff1a; 一、信息 1.f(x)的表达式 2.目标求这个f(x)的n阶麦克劳林公式 二、分析 条件1&#xff1a;告诉我f(x)的表达式为我后续带入公式做准备 条件2&#xff1a;告诉我用什么公式和此次求解的方向 三…

【ONE·C++ || 二叉搜索树】

总言 二叉树进阶&#xff1a;主要介绍二叉搜索树相关内容。 文章目录 总言1、基本介绍1.1、什么是二叉搜索树 2、相关实现2.1、基本框架2.1.1、如何构建二叉树单节点2.1.2、如何定义一个二叉搜索树 2.2、非递归实现&#xff1a;插入、查找、删除2.2.1、二叉搜索树插入&#xf…

系统集成项目管理工程师 笔记(第16章:变更管理)

文章目录 16.1.2 项目变更的分类 50416.1.3 项目变更产生的原因 50516.2 变更管理的基本原则 50516.3 变更管理角色职责与工作程序 50616.3.1 角色职责 50716.3.2 工作程序 50716.4.1 变更管理操作要点 511 第16章 变更管理 504 16.1 项目变更的基本概念 504 项目变更是指在信息…

05 KVM虚拟化Linux Bridge环境部署

文章目录 05 KVM虚拟化Linux Bridge环境部署5.1 安装Linux Bridge5.1.1 安装bridge-utils软件包5.1.2 确认安装是否成功 5.2 配置Linux Bridge5.2.1 创建网桥br05.2.2 将物理网卡ens33绑定到Linux Bridge5.2.3 配置ens33的ip5.2.4 为Linux Bridge网桥br0分配ip5.2.4.1 DHCP设置…

JavaWeb04(登录绑值模糊查询功能实现连接数据库)

目录 一.实现登录功能 2.2 制作简易验证码 2.3 完成登录验证 2.4 登录实现 ①连接字符串 private static final String URL"jdbc:oracle:thin:localhost:1521:orcl"; ②加载驱动 OracleDriver private static final String URL"jdbc:oracle:thin:localh…

更好地提问ChatGPT_常用prompt表

对常见的用途&#xff0c;记录该表以便获得ChatGPT更高质量的回复。 类别目的提问方式要点文案写作周报、日报、年终总结本周我做了以下几件事情&#xff1a;出差客户办事处、交流演示、初步数据分析。请帮我写一份周报要点形式列举工作内容。可以说明职位&#xff0c;以便工作…

【五一创作】web小游戏开发:华容道(一)

web小游戏开发:华容道(一) 华容道htmlcss素材原图素材验证游戏关卡华容道 老顾儿时的记忆啊,也是一个经典的益智游戏。 游戏规则就不用再介绍了吧,就是让曹操移动到曹营就算胜利。 CSDN 文盲老顾的博客,https://blog.csdn.net/superrwfei html 经过上次的扫雷,大家应…

JavaScript 内存泄漏

内存的释放流程&#xff1a; 分配内存内存中的读写垃圾回收 对于内存的使用&#xff0c;所有语言基本都是一样的&#xff0c;只是更底层的语言在对于 ”分配内存“ 和 ”使用内存“ 是明确的&#xff0c;但是在高级语言中&#xff08;比如本文的 JS&#xff09;是隐藏了。 JS …

『python爬虫』05. requests模块入门(保姆级图文)

目录 安装requests1. 抓取搜狗搜索内容 requests.get2. 抓取百度翻译数据 requests.post3. 豆瓣电影喜剧榜首爬取4. 关于请求头和关闭request连接总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 安装requests …

Spring 中的顺序问题(别迟疑就是你想知道的顺序问题)

Spring 中的一些顺序问题 Spring 中的顺序问题并不是不重要&#xff0c;比如多个 BeanFactoryPostProcessor如何知道先执行哪一个&#xff1b;为什么自定义的 Bean 可以覆盖默认的自动装配的 Bean&#xff1b;AOP 拦截器链中拦截器的顺序是如何确定的等等问题。 相信看完这篇…

(数学知识)试除法判断质数,分解质因数,埃式与线性筛质数

质数 质数是指在大于1的自然数中&#xff0c;除了1和它本身以外不再有其他因数的自然数。 试除法判定质数 你会发现如果说一个数要分成两个数相乘的话&#xff0c;那么这两个数肯定都是成对出现的&#xff0c;有一大一小的相对关系。因此不需要从2遍历到n&#xff0c;循环的…

AI智能课程:第七讲-不会写代码?有chatGPT不用慌

chatGPT辅助自动化测试-常见用途 根据代码写需求 作为python编程小白&#xff0c;如何安装python环境 ### 怎么用python发送http请求 如何在http请求中&#xff0c;请求头带上token值&#xff0c;怎么发送请求 websrvice协议&#xff0c;用python语言基于websrvice发送请求…

【数据结构】第十三站:排序(下)归并排序

文章目录 一、归并排序递归法1.归并排序的基本思想2.归并排序的代码实现 二、归并排序非递归1.可否使用栈来模拟&#xff1f;2.直接改非递归(简化版)3.处理边界之一把梭哈4.处理边界之归并一次拷贝一次 一、归并排序递归法 1.归并排序的基本思想 归并排序&#xff08;MERGE-SO…

网络安全事件调查,如何追溯攻击者的行踪和攻击路径

在当今互联网的世界里&#xff0c;网络安全已成为一个非常重要的话题。随着网络攻击的不断增加&#xff0c;如何保护我们的网络安全已成为一个严峻的挑战。为了防止网络攻击&#xff0c;需要了解攻击者的行踪和攻击路径&#xff0c;以便更好地预防和应对网络攻击。 网络安全事…