责任链模式让我的代码精简10倍?
目录
- 什么是责任链
- 使用场景
- 结语

前言
最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。实际上,针对导入功能,我认为模版方法更合适!为此,隔壁团队也拿出我们的案例,进行了集体 code review。学好设计模式,且不要为了练习,强行使用!让原本 100 行就能实现的功能,写了 3000 行!对错暂且不论,我们先一起看看责任链设计模式吧!什么是责任链
责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

使用场景
责任链的使用场景还是比较多的:
- 多条件流程判断:权限控制
- ERP 系统流程审批:总经理、人事经理、项目经理
- Java 过滤器的底层实现 Filter
反例
假设现在有一个闯关游戏,进入下一关的条件是上一关的分数要高于 xx:- 游戏一共 3 个关卡
- 进入第二关需要第一关的游戏得分大于等于 80
- 进入第三关需要第二关的游戏得分大于等于 90
public class FirstPassHandler {
public int handler(){
System.out.println( "第一关-->FirstPassHandler");
return 80;
}
}
//第二关
public class SecondPassHandler {
public int handler(){
System.out.println( "第二关-->SecondPassHandler");
return 90;
}
}
//第三关
public class ThirdPassHandler {
public int handler(){
System.out.println( "第三关-->ThirdPassHandler,这是最后一关啦");
return 95;
}
}
//客户端
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler(); //第一关
SecondPassHandler secondPassHandler = new SecondPassHandler(); //第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler(); //第三关
int firstScore = firstPassHandler.handler();
//第一关的分数大于等于80则进入第二关
if(firstScore >= 80){
int secondScore = secondPassHandler.handler();
//第二关的分数大于等于90则进入第二关
if(secondScore >= 90){
thirdPassHandler.handler();
}
}
}
}
那么如果这个游戏有 100 关,我们的代码很可能就会写成这个样子: if(第 1关通过){
// 第2关 游戏
if(第 2关通过){
// 第3关 游戏
if(第 3关通过){
// 第4关 游戏
if(第 4关通过){
// 第5关 游戏
if(第 5关通过){
// 第6关 游戏
if(第 6关通过){
//...
}
}
}
}
}
}
这种代码不仅冗余,并且当我们要将某两关进行调整时会对代码非常大的改动,这种操作的风险是很高的,因此,该写法非常糟糕。
初步改造
如何解决这个问题,我们可以通过链表将每一关连接起来,形成责任链的方式,第一关通过后是第二关,第二关通过后是第三关....这样客户端就不需要进行多重 if 的判断了: public class FirstPassHandler { /**
* 第一关的下一关是 第二关
*/
private SecondPassHandler secondPassHandler;
public void setSecondPassHandler(SecondPassHandler secondPassHandler) {
this.secondPassHandler = secondPassHandler;
}
//本关卡游戏得分
private int play(){
return 80;
}
public int handler(){
System.out.println( "第一关-->FirstPassHandler");
if(play() >= 80){
//分数>=80 并且存在下一关才进入下一关
if( this.secondPassHandler != null){
return this.secondPassHandler.handler();
}
}
return 80;
}
}
public class SecondPassHandler {
/**
* 第二关的下一关是 第三关
*/
private ThirdPassHandler thirdPassHandler;
public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {
this.thirdPassHandler = thirdPassHandler;
}
//本关卡游戏得分
private int play(){
return 90;
}
public int handler(){
System.out.println( "第二关-->SecondPassHandler");
if(play() >= 90){
//分数>=90 并且存在下一关才进入下一关
if( this.thirdPassHandler != null){
return this.thirdPassHandler.handler();
}
}
return 90;
}
}
public class ThirdPassHandler {
//本关卡游戏得分
private int play(){
return 95;
}
/**
* 这是最后一关,因此没有下一关
*/
public int handler(){
System.out.println( "第三关-->ThirdPassHandler,这是最后一关啦");
return play();
}
}
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler(); //第一关
SecondPassHandler secondPassHandler = new SecondPassHandler(); //第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler(); //第三关
firstPassHandler.setSecondPassHandler(secondPassHandler); //第一关的下一关是第二关
secondPassHandler.setThirdPassHandler(thirdPassHandler); //第二关的下一关是第三关
//说明:因为第三关是最后一关,因此没有下一关
//开始调用第一关 每一个关卡是否进入下一关卡 在每个关卡中判断
firstPassHandler.handler();
}
}
缺点
现有模式的缺点:- 每个关卡中都有下一关的成员变量并且是不一样的,形成链很不方便
- 代码的扩展性非常不好
责任链改造
既然每个关卡中都有下一关的成员变量并且是不一样的,那么我们可以在关卡上抽象出一个父类或者接口,然后每个具体的关卡去继承或者实现。有了思路,我们先来简单介绍一下责任链设计模式的基本组成:- 抽象处理者(Handler)角色: 定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色: 实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色: 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

public abstract class AbstractHandler {
/**
* 下一关用当前抽象类来接收
*/
protected AbstractHandler next;
public void setNext(AbstractHandler next) {
this.next = next;
}
public abstract int handler();
}
public class FirstPassHandler extends AbstractHandler{
private int play(){
return 80;
}
@Override
public int handler(){
System.out.println( "第一关-->FirstPassHandler");
int score = play();
if(score >= 80){
//分数>=80 并且存在下一关才进入下一关
if( this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class SecondPassHandler extends AbstractHandler{
private int play(){
return 90;
}
public int handler(){
System.out.println( "第二关-->SecondPassHandler");
int score = play();
if(score >= 90){
//分数>=90 并且存在下一关才进入下一关
if( this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class ThirdPassHandler extends AbstractHandler{
private int play(){
return 95;
}
public int handler(){
System.out.println( "第三关-->ThirdPassHandler");
int score = play();
if(score >= 95){
//分数>=95 并且存在下一关才进入下一关
if( this.next != null){
return this.next.handler();
}
}
return score;
}
}
public class HandlerClient {
public static void main(String[] args) {
FirstPassHandler firstPassHandler = new FirstPassHandler(); //第一关
SecondPassHandler secondPassHandler = new SecondPassHandler(); //第二关
ThirdPassHandler thirdPassHandler = new ThirdPassHandler(); //第三关
// 和上面没有更改的客户端代码相比,只有这里的set方法发生变化,其他都是一样的
firstPassHandler.setNext(secondPassHandler); //第一关的下一关是第二关
secondPassHandler.setNext(thirdPassHandler); //第二关的下一关是第三关
//说明:因为第三关是最后一关,因此没有下一关
//从第一个关卡开始
firstPassHandler.handler();
}
}
责任链工厂改造
对于上面的请求链,我们也可以把这个关系维护到配置文件中或者一个枚举中。我将使用枚举来教会大家怎么动态的配置请求链并且将每个请求者形成一条调用链。
public enum GatewayEnum {
// handlerId, 拦截者名称,全限定类名,preHandlerId,nextHandlerId
API_HANDLER( new GatewayEntity( 1, "api接口限流", "cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler", null, 2)),
BLACKLIST_HANDLER( new GatewayEntity( 2, "黑名单拦截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler", 1, 3)),
SESSION_HANDLER( new GatewayEntity( 3, "用户会话拦截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler", 2, null)),
;
GatewayEntity gatewayEntity;
public GatewayEntity getGatewayEntity() {
return gatewayEntity;
}
GatewayEnum(GatewayEntity gatewayEntity) {
this.gatewayEntity = gatewayEntity;
}
}
public class GatewayEntity {
private String name;
private String conference;
private Integer handlerId;
private Integer preHandlerId;
private Integer nextHandlerId;
}
public interface GatewayDao {
/**
* 根据 handlerId 获取配置项
* @param handlerId
* @return
*/
GatewayEntity getGatewayEntity(Integer handlerId);
/**
* 获取第一个处理者
* @return
*/
GatewayEntity getFirstGatewayEntity();
}
public class GatewayImpl implements GatewayDao {
/**
* 初始化,将枚举中配置的handler初始化到map中,方便获取
*/
private static Map<Integer, GatewayEntity> gatewayEntityMap = new HashMap<>();
static {
GatewayEnum[] values = GatewayEnum.values();
for (GatewayEnum value : values) {
GatewayEntity gatewayEntity = value.getGatewayEntity();
gatewayEntityMap.put(gatewayEntity.getHandlerId(), gatewayEntity);
}
}
@Override
public GatewayEntity getGatewayEntity(Integer handlerId) {
return gatewayEntityMap.get(handlerId);
}
@Override
public GatewayEntity getFirstGatewayEntity() {
for (Map.Entry<Integer, GatewayEntity> entry : gatewayEntityMap.entrySet()) {
GatewayEntity value = entry.getValue();
// 没有上一个handler的就是第一个
if (value.getPreHandlerId() == null) {
return value;
}
}
return null;
}
}
public class GatewayHandlerEnumFactory {
private static GatewayDao gatewayDao = new GatewayImpl();
// 提供静态方法,获取第一个handler
public static GatewayHandler getFirstGatewayHandler() {
GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();
GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);
if (firstGatewayHandler == null) {
return null;
}
GatewayEntity tempGatewayEntity = firstGatewayEntity;
Integer nextHandlerId = null;
GatewayHandler tempGatewayHandler = firstGatewayHandler;
// 迭代遍历所有handler,以及将它们链接起来
while ((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null) {
GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);
GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);
tempGatewayHandler.setNext(gatewayHandler);
tempGatewayHandler = gatewayHandler;
tempGatewayEntity = gatewayEntity;
}
// 返回第一个handler
return firstGatewayHandler;
}
/**
* 反射实体化具体的处理者
* @param firstGatewayEntity
* @return
*/
private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {
// 获取全限定类名
String className = firstGatewayEntity.getConference();
try {
// 根据全限定类名,加载并初始化该类,即会初始化该类的静态段
Class<?> clazz = Class.forName(className);
return (GatewayHandler) clazz.newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
public class GetewayClient {
public static void main(String[] args) {
GetewayHandler firstGetewayHandler = GetewayHandlerEnumFactory.getFirstGetewayHandler();
firstGetewayHandler.service();
}
}
结语
设计模式有很多,责任链只是其中的一种,我觉得很有意思,非常值得一学。设计模式确实是一门艺术,仍需努力呀!
相关文章:

责任链模式让我的代码精简10倍?
目录 什么是责任链使用场景结语 前言最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。实际上,针对导入功能,我认为模版方法更合适ÿ…...

Draw软件安装下载
Draw软件安装下载 1.软件简介2.软件下载3.安装方法 1.软件简介 Draw软件,全名为LibreOffice Draw,是一款免费、开源的2D矢量绘图软件,属于LibreOffice办公套件的一部分。它可以用来创建各种类型的图形,包括流程图、组织结构图、平…...
uniapp代码混淆ios上架43问题
参考文章:uniapp打包ios apk,混淆代码_uniapp 混淆_酸奶自由竟然重名了的博客-CSDN博客 uniapp打包ios,上传到ios应用市场时,会因为 4.3(代码重复率过高) 无法通过审核,此时可通过混淆代码来通过审核 1. 项目终端 安…...

Linux目录遍历函数
1.打开一个目录 #include <sys/types.h> #include <dirent.h> DIR *opendir(const char *name); 参数: -name:需要打开的目录的名称 返回值: DIR * 类型,理解为目录流 错误返回NULL 2.读取目录中的数据 #include <dirent.h…...

数据库-理论基础
目录 1.什么是数据库? 2.数据库与文件系统的区别? 3.常见的数据库由那些? 4.关系型数据库(MySQL)的特征及组成结构介绍 1.什么是数据库? 数据:描述事物的符号记录,可以是数字,文…...

【已解决】src/spt_python.h:14:20: 致命错误:Python.h:没有那个文件或目录
src/spt_python.h:14:20: 致命错误:Python.h:没有那个文件或目录 问题 其中重点的报错信息 src/spt_python.h:14:20: fatal error: Python.h: No such file or directory 思路 sudo yum install python-devel然后重新安装需要的依赖。 解决 成功。…...

基于Face++网络爬虫+人脸融合算法智能发型推荐程序——深度学习算法应用(含Python及打包exe工程源码)+爬虫数据集
目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境Pycharm 环境 模块实现1. Face.APl调用1)Face.APl介绍2)调用API 2. 数据爬取1)网络数据爬取步骤2)爬虫实现 3. 模型构建4. 用户界面设计1)需要调用的库文…...

Jetson nano嵌入式平台配置ip记录
背景 Jetson nano平台使用千兆网和PC连接时没有ip地址,在ubuntu的终端输入ifconfig显示eh0未设置ip,需要先在nano平台上配置ip地址,然后PC通过千兆网远程控制该平台。 配置ip 使用终端进入到network文件夹中, cd /etc/network…...

前端中的跨域请求及其解决方案
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 跨域(Cross-Origin)⭐CORS(跨域资源共享)⭐JSONP(JSON with Padding)⭐代理服务器⭐ WebSocket⭐服务器设置响应头⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:…...

SpringBoot2.0(mybatis-plus初始使用)
目录 一,介绍二,SpringBoot2.x整合MybatisPlus Lombok2.1,添加依赖 pom2.2,配置数据库信息 application.properties2.3,工程结构初始化 三,创建接口返回统一对象四,创建bean五,创建…...

游戏视频录制软件对比,哪款最适合你的需求?
随着电子竞技和游戏直播行业的迅速崛起,越来越多的玩家渴望记录并分享自己在游戏中的精彩瞬间。游戏视频录制软件正是满足这一需求的关键工具。本文将针对三款优秀的游戏视频录制软件进行对比分析,以便为读者提供选购建议。 游戏视频录制软件1࿱…...

耐蚀合金连续油管最新版 学习记录
声明 本文是学习GB-T 42858-2023 耐蚀合金连续油管. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件规定了耐蚀合金连续油管的订货、材料、制造、检验试验、标记等。 本文件适用于油气井用耐蚀合金连续油管(以下简称"油管")…...

LoGoNet:基于局部到全局跨模态融合的精确 3D 目标检测
论文地址:https://arxiv.org/abs/2303.03595 论文代码:https://github.com/sankin97/LoGoNet 论文背景 激光雷达传感器点云通常是稀疏的,无法提供足够的上下文来区分远处的区域,从而造成性能次优。 激光雷达-摄像机融合方法在三…...
Python 多线程、线程池、进程池
线程间的通讯机制 消息队列 event 事件对象 当线程创建完成之后,并不会马上执行线程,而是等待某一事件发生,线程才会启动 import threading# # 创建 event 对象 # event threading.Event() # # 重置代码中的 event 对象,使得所…...

深入浅出了解华为端到端交付流程的概念和5个关键点
如果您或您所在的组织在学习和研究华为,那么对“端到端”这个词语就一点都不陌生。 今天华研荟带着您了解华为端到端的交付流程的思想和一些做法,如果了解了这个,那么对于华为在其他领域提出的端到端要求或做法就一通百通了。 一、什么是端…...

[Linux]进程信号
[Linux]进程信号 文章目录 [Linux]进程信号进程信号的定义信号的特点信号的生命过程发送信号的原理进程处理信号的方式分类使用指令查看Linux系统定义的信号信号产生使用终端按键产生信号使用指令向进程发送信号调用系统调用向进程发送信号由软件条件产生信号硬件异常产生信号 …...

PostgreSQL 数据类型
文章目录 PostgreSQL数据类型说明PostgreSQL数据类型使用单引号和双引号数据类型转换布尔类型数值类型整型浮点型序列数值的常见操作 字符串类型日期类型枚举类型IP类型JSON&JSONB类型复合类型数组类型 PostgreSQL数据类型说明 PGSQL支持的类型特别丰富,大多数…...

智慧港口4G+UWB+GPS/北斗RTK人员定位系统解决方案
港口人员定位系统能够帮助企业实现对港口作业人员的全面监控和管理,不仅可以保障人员的人身安全,还可以提高人员的作业效率,为港口的可持续发展提供有力保障。接下来为大家分享智慧港口人员定位系统解决方案。 方案背景 1、港口作业人员多&a…...

实时时钟和日历电路芯片MS85163/MS85163M
MS85163/MS85163M 是一款 CMOS 实时时钟 (RTC) 和 日历电路,针对低功耗进行了优化,内置了可编程的时钟输出、中断输出和低电压检测器。所有寄存器地址和数据都通过两线双向I 2 C 总线进行串行传输,最大总线传输速度为 400kbit/s 。采用SOP8…...

【Java从入门到精通】这也许就是Java火热的原因吧!
前言:Java是一种高级的、面向对象的、可跨平台的程序设计语言。Java根据技术类别可划分为以下几类:JavaSE(Standard Edition,标准版):支持面向桌面、嵌入式和移动设备的应用程序开发;JavaEE&…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...