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

责任链模式

责任链模式

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它用于将请求的发送者和接收者解耦,使多个对象都有机会处理请求。这种模式建立在一个处理对象的链上,每个处理对象都可以选择处理请求或者将请求传递给链上的下一个处理对象。

1、责任链模式角色

在 Java 中,责任链模式的实现通常包括以下几个要素:

  1. 抽象处理者(Handler):定义一个处理请求的接口,通常包括一个处理方法(例如:handleRequest())和一个设置下一个处理者的方法(例如:setNextHandler())。抽象处理者可以是一个接口或者抽象类。
  2. 具体处理者(ConcreteHandler):实现抽象处理者接口,具体处理不同的请求。如果当前处理者无法处理请求,它可以将请求传递给下一个处理者。
  3. 责任链(Chain ofResponsibility):将一系列的处理者连接成一个链,形成处理请求的链式结构。通常在客户端代码中构建这个责任链,并将请求从链的开头传递给第一个处理者。

在这里插入图片描述

2、责任链模式适用业务场景

  1. 日志记录系统:在系统中实现日志记录功能时,可以使用责任链模式。不同的日志级别(如调试、信息、警告、错误)可以由不同的处理器来处理,从而根据需要将日志记录到不同的目标(文件、数据库、控制台等)。
  2. 审批流程:在企业应用中,审批流程可能涉及多个层级的审批,每个层级的审批者都可以决定是否通过审批。责任链模式可以用于构建这样的审批流程,其中每个处理器代表一个审批者,如果一个审批者无法处理,请求将传递给下一个审批者。
  3. 安全认证:在安全认证中,可以使用责任链模式来实现多层级的认证机制。每个认证处理器可以负责一个特定的认证方法(如用户名密码、指纹、令牌等)。如果一个认证处理器无法通过认证,系统可以继续尝试下一个处理器。
  4. HTTP 请求处理:在 Web 应用程序中,HTTP
    请求处理可以分成多个环节,例如身份验证、授权、输入验证、缓存等。责任链模式可以用于将每个环节拆分成一个处理器,并将请求从一个处理器传递到下一个处理器。
  5. 异常处理链:在处理异常时,可能需要一系列处理步骤来处理不同类型的异常。责任链模式可以用于将异常处理逻辑分解成多个处理器,每个处理器负责处理一种类型的异常。
  6. 请求过滤器:在 Web
    开发中,请求过滤器可以用于对请求进行预处理,例如请求参数验证、安全检查等。责任链模式可以用于将不同的过滤逻辑拆分成多个处理器,依次对请求进行处理。
  7. 游戏闯关系统:在游戏中,角色的闯关系统可以采用责任链模式。每层关卡可以由不同的处理器来处理,如果一个处理器处理完成当前关卡,结果打到过关条件,系统可以使用下一个处理器进行下一关处理。

总之,责任链模式在任何需要将处理逻辑拆分成独立步骤,并且这些步骤可以灵活组合的情况下都是有用的。它帮助减少耦合,使代码更加可扩展和可维护。

3、游戏闯关系统责任链模式的应用

需求描述

假设现在有一个闯关游戏,进入下一关的条件是上一关的分数要高于 xx:

游戏一共 3 个关卡
进入第二关需要第一关的游戏得分大于等于 90
进入第三关需要第二关的游戏得分大于等于 80

普通业务代码实现

//第一关
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();//第一关的分数大于等于90则进入第二关if(firstScore >= 90){int secondScore = secondPassHandler.handler();//第二关的分数大于等于80则进入第二关if(secondScore >= 80){thirdPassHandler.handler();}}}
}

那么如果这个游戏有 99 关,我们的代码很可能就会写成这个样子:

if(1关通过){// 第2关 游戏if(2关通过){// 第3关 游戏if(3关通过){// 第4关 游戏if(4关通过){// 第5关 游戏if(5关通过){// 第6关 游戏if(6关通过){//...}}} }}
}

这种代码不仅冗余,并且当我们要将某两关进行调整时会对代码非常大的改动,这种操作的风险是很高的,因此,该写法非常糟糕。

责任链改造代码

如何解决这个问题,我们可以通过链表将每一关连接起来,形成责任链的方式,第一关通过后是第二关,第二关通过后是第三关…

这样客户端就不需要进行多重 if 的判断了:

public abstract class Handler {/*** 下一关用当前抽象接口来接收*/protected Handler next;public void setNext(Handler next) {this.next = next;}public abstract int handler();
}

public class FirstPassHandler extends Handler{final int firstPassScore = 90;private int play(){return firstPassScore;}@Overridepublic int handler(){System.out.println("第一关-->FirstPassHandler");int score = play();if(score >= firstPassScore){//分数>=firstPassScore 并且存在下一关才进入下一关if(this.next != null){return this.next.handler();}}return score;}
}
package com.lf.java.designpattern.chain;public class SecondPassHandler extends Handler{final int SecondPassScore = 80;private int play(){return SecondPassScore;}public int handler(){System.out.println("第二关-->SecondPassHandler");int score = play();if(score >= SecondPassScore){//分数>=SecondPassScore 并且存在下一关才进入下一关if(this.next != null){return this.next.handler();}}return score;}
}
package com.lf.java.designpattern.chain;public class ThirdPassHandler extends Handler{final int SecondPassScore = 70;private int play(){return SecondPassScore;}public int handler(){System.out.println("第三关-->ThirdPassHandler");int score = play();if(score >= SecondPassScore){//分数>=SecondPassScore 并且存在下一关才进入下一关if(this.next != null){return this.next.handler();}}return score;}
}
package com.lf.java.designpattern.chain;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();}
}

改造完成的代码请求会从链的开头传递到每个处理器,根据请求的内容,每个处理器都可以选择处理请求或者将请求传递给下一个处理器。这样的设计使得责任链可以根据需要动态地调整和扩展。

但是还不能自动化的添加对应的链之间的关系。

责任链工厂改造代码

对于上面的请求链,我们也可以把这个关系维护到配置文件中或者一个枚举中。将使用枚举来动态的配置请求链并且将每个请求者形成一条调用链。

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class PassEntity {/** 处理器顺序id */private Integer handlerId;/** 业务处理器名称*/private String name;/** 全限定类名 */private String conference;/** 前置处理器 */private Integer preHandlerId;/** 前置处理器 */private Integer nextHandlerId;
}
public enum PassEnum {// handlerId, 拦截者名称,全限定类名,preHandlerId,nextHandlerIdAPI_HANDLER(new PassEntity(1, "第一关", "com.lf.java.design.pattern.chain.FirstPassHandler", null, 2)),BLACKLIST_HANDLER(new PassEntity(2, "第二关", "com.lf.java.design.pattern.chain.SecondPassHandler", 1, 3)),SESSION_HANDLER(new PassEntity(3, "第三关", "com.lf.java.design.pattern.chain.ThirdPassHandler", 2, null)),;PassEntity passEntity;public PassEntity getPassEntity() {return passEntity;}PassEnum(PassEntity passEntity) {this.passEntity = passEntity;}
}
public interface IPassService {/*** 根据 handlerId 获取配置项* @param handlerId* @return*/PassEntity getPassEntity(Integer handlerId);/*** 获取第一个处理者* @return*/PassEntity getFirstPassEntity();
}
package com.lf.java.designpattern.chain;import java.util.HashMap;
import java.util.Map;public class PassServiceImpl implements IPassService {/*** 初始化,将枚举中配置的handler初始化到map中,方便获取*/private static Map<Integer, PassEntity> passEntityMap = new HashMap<>();static {PassEnum[] values = PassEnum.values();for (PassEnum value : values) {PassEntity passEntity = value.getPassEntity();passEntityMap.put(passEntity.getHandlerId(), passEntity);}}@Overridepublic PassEntity getPassEntity(Integer handlerId) {return passEntityMap.get(handlerId);}@Overridepublic PassEntity getFirstPassEntity() {for (Map.Entry<Integer, PassEntity> entry : passEntityMap.entrySet()) {PassEntity value = entry.getValue();//  没有上一个handler的就是第一个if (value.getPreHandlerId() == null) {return value;}}return null;}
}
package com.lf.java.designpattern.chain;public class PassHandlerEnumFactory {private static IPassService passService = new PassServiceImpl();// 提供静态方法,获取第一个handlerpublic static Handler getFirstPassHandler() {PassEntity firstPassEntity = passService.getFirstPassEntity();Handler firstPassHandler = newPassHandler(firstPassEntity);if (firstPassHandler == null) {return null;}PassEntity tempPassEntity = firstPassEntity;Integer nextHandlerId = null;Handler tempPassHandler = firstPassHandler;// 迭代遍历所有handler,以及将它们链接起来while ((nextHandlerId = tempPassEntity.getNextHandlerId()) != null) {PassEntity PassEntity = passService.getPassEntity(nextHandlerId);Handler PassHandler = newPassHandler(PassEntity);tempPassHandler.setNext(PassHandler);tempPassHandler = PassHandler;tempPassEntity = PassEntity;}// 返回第一个handlerreturn firstPassHandler;}/*** 反射实体化具体的处理者* @param firstPassEntity* @return*/private static Handler newPassHandler(PassEntity firstPassEntity) {// 获取全限定类名String className = firstPassEntity.getConference();try {// 根据全限定类名,加载并初始化该类,即会初始化该类的静态段Class<?> clazz = Class.forName(className);return (Handler) clazz.newInstance();} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {e.printStackTrace();}return null;}}

测试业务类

public class PassHandlerClient {public static void main(String[] args) {Handler firstPassHandler = PassHandlerEnumFactory.getFirstPassHandler();firstPassHandler.handler();}
}

运行结果:

第一关-->FirstPassHandler
第二关-->SecondPassHandler
第三关-->ThirdPassHandler

这样通过动态的配置请求链就可以自动将每个请求者形成一条调用链。

还有更为复杂的链的形成,比如业务的链里面有复合链,而复合链又是普通的功能链组成的。这种责任链工厂方式更能充分体现出代码设计优势。

相关文章:

责任链模式

责任链模式 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;它用于将请求的发送者和接收者解耦&#xff0c;使多个对象都有机会处理请求。这种模式建立在一个处理对象的链上&#xff0c;每个处理对象都可以选择处理请求或…...

【BI看板】Docker-compose安装Superset,安装最新版本2.1.0

软件及环境准备 docker&#xff0c; docker-compose docker-compose安装 字节码安装 #wget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64 #mv docker-compose-linux-x86_64 docker-compose #chmod x /usr/local/bin/docker-com…...

VS2019生成的DLL,给QT(MinGW版本)使用的小结

VS2019端&#xff1a; a 基于生成一个DLL的工程&#xff08;要注意生成是x86&#xff0c;还是x64的&#xff0c;需要和后面的QT的App工程对应&#xff09;&#xff0c;这里不多解释了&#xff0c;网上多的是&#xff1b; b 在cpp实现文件里&#xff0c;假如要导出一个这样的…...

c++--SLT六大组件之间的关系

1.SLT六大组件&#xff1a; 容器&#xff0c;迭代器&#xff0c;算法&#xff0c;仿函数&#xff0c;适配器&#xff0c;空间配置器 2.六大组件之间的关系 容器&#xff1a;容器是STL最基础的组件&#xff0c;没有容器&#xff0c;就没有数据&#xff0c;容器的作用就是用来存…...

解析个人信息保护影响评估

一、个人信息保护影响评估的概念及范围&#xff08;What&#xff09; 什么是“个人信息保护影响评估”&#xff1f;如何理解&#xff1f;“个人信息保护影响评估”的概念未在我国高位阶的法律规定中明确&#xff0c;其历经从观念到实践的演变&#xff0c;逐渐形成业界普遍认可…...

2.阿里云对象存储OSS

1.对象存储概述 文件上传&#xff0c;是指将本地图片、视频、音频等文件上传到服务器上&#xff0c;可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛&#xff0c;我们经常发抖音、发朋友圈都用到了文件上传功能。 实现文件上传服务&#xff0c;需要有存储的支持…...

(三)Unity开发Vision Pro——入门

3.入门 1.入门 本节涵盖了几个重要主题&#xff0c;可帮助您加快visionOS 平台开发速度。在这里&#xff0c;您将找到构建第一个 Unity PolySpatial XR 应用程序的分步指南的链接&#xff0c;以及 PolySpatial XR 开发时的一些开发最佳实践。 2.开发与迭代 有关先决条件、开…...

召集令:CloudQuery 社区有奖征文活动来啦!

CloudQuery 社区第一期征文活动来袭&#xff01;&#xff01;&#xff01;只要你对 CloudQuery 产品感兴趣&#xff0c;或者是希望了解 CQ &#xff0c;都可以来参加&#xff0c;在本期活动中&#xff0c;我们也为大家准备了多种主题供你选择&#xff0c;CQ 使用案例、版本对比…...

【傅里叶级数与傅里叶变换】数学推导——1、基础知识点回顾及[Part1:三角函数的正交性]介绍

文章内容来自DR_CAN关于傅里叶变换的视频&#xff0c;本篇文章提供了一些基础知识点&#xff0c;比如三角函数常用的导数、三角函数换算公式等。 文章全部链接&#xff1a; 基础知识点 Part1&#xff1a;三角函数系的正交性 Part2&#xff1a;T2π的周期函数的傅里叶级数展开 P…...

BUUCTF [MRCTF2020]Ezpop解题思路

题目代码 Welcome to index.php <?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier {protected $var;publi…...

【IMX6ULL驱动开发学习】07.驱动程序分离的思想之平台总线设备驱动模型和设备树

一、驱动程序分离的思想 【IMX6ULL驱动开发学习】05.字符设备驱动开发模板&#xff08;包括读写函数、poll机制、异步通知、定时器、中断、自动创建设备节点和环形缓冲区&#xff09;_阿龙还在写代码的博客-CSDN博客 之前编写驱动程序的代码存在不少弊端&#xff1a;移植性差…...

深度学习中的python语法笔记总结

解释 torch中的 .clamp(min0) 在PyTorch中&#xff0c;torch.clamp将张量中的元素限制在指定的范围内。 torch.clamp(min0)会将张量中的每个元素与0进行比较&#xff0c;并将小于0的元素替换为0。其他大于等于0的元素则保持不变。 clamp函数原理 def clamp(x, lower, upper)…...

Reids 的整合使用

大家好 , 我是苏麟 , 今天带来强大的Redis . REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统&#xff0c;是跨平台的非关系型数据库。 Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选…...

Vue3 —— watchEffect 高级侦听器

该文章是在学习 小满vue3 课程的随堂记录示例均采用 <script setup>&#xff0c;且包含 typescript 的基础用法 前言 Vue3 中新增了一种特殊的监听器 watchEffect&#xff0c;它的类型是&#xff1a; function watchEffect(effect: (onCleanup: OnCleanup) > void,o…...

Java异步子线程读取主线程参数的若干好玩场景

在开发过程中&#xff0c;我们难免会因为性能、实时响应等&#xff0c;需要异步处理的一些事务&#xff0c;并且在子线程中有时我们还需要获取主线程相关的参数。下面有若干方案可以实现上述场景&#xff0c;但会出现一定的问题。 场景1-基础场景 在主线程中开启子线程&#x…...

Android 视频开发

在 Android 平台上进行视频开发&#xff0c;您需要掌握以下关键知识点&#xff0c;以确保能够成功地开发和调试视频应用程序&#xff1a; Android视频架构&#xff1a; 了解 Android 的视频系统架构&#xff0c;包括视频捕获、编码、解码、渲染和显示等。 视频格式和编解码&am…...

【计算机网络篇】UDP协议

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; UDP协议 1&#xff0c;UDP 简介 UDP&#xff08;User Datagram Protocol&#xff09;是一种无连…...

LeetCode 2682. 找出转圈游戏输家

【LetMeFly】2682.找出转圈游戏输家 力扣题目链接&#xff1a;https://leetcode.cn/problems/find-the-losers-of-the-circular-game/ n 个朋友在玩游戏。这些朋友坐成一个圈&#xff0c;按 顺时针方向 从 1 到 n 编号。从第 i 个朋友的位置开始顺时针移动 1 步会到达第 (i …...

数据结构单链表

单链表 1 链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 。 在我们开始讲链表之前&#xff0c;我们是写了顺序表&#xff0c;顺序表就是类似一个数组的东西&#xff0…...

自定义WEB框架结合Jenkins实现全自动测试

自定义WEB框架结合Jenkins实现全自动测试 allure生成 allure生成 1.allure–纯命令运行 -固定的–稍微记住对应的单词即可。2 安装&#xff0c;2个步骤: 1.下载allure包&#xff0c;然后配置环境变量。 https://github.com/allure-framework/allure2/releases/tag/2.22.4 2.在…...

React-PDF自定义字体粗细终极指南:实现精确文本字重控制的完整教程

React-PDF自定义字体粗细终极指南&#xff1a;实现精确文本字重控制的完整教程 【免费下载链接】react-pdf &#x1f4c4; Create PDF files using React 项目地址: https://gitcode.com/gh_mirrors/re/react-pdf React-PDF是一个功能强大的库&#xff0c;允许开发者使用…...

【大模型调优】彻底洗掉论文“机器味”:DeepSeek/Kimi/豆包专属降AI指令与保姆级工作流

很多时候大学生写论文逻辑太严谨、话术太规范&#xff0c;反而会导致AI率过高&#xff0c;且一旦AI率过高&#xff0c;轻则退回重改&#xff0c;重则取消答辩资格&#xff0c;这后果谁都担不起。 为了帮大家有效降低aigc率&#xff0c;这周我专门针对目前市面上最主流的三款大…...

Linux性能调优实战:CPU与内存优化指南

Linux 性能调优实战指南1. 性能优化基础概念1.1 性能指标Linux性能优化的两个核心指标是吞吐量和延迟。从应用负载角度看&#xff0c;直接影响终端用户体验&#xff1b;从系统资源角度看&#xff0c;关注资源使用率和饱和度。性能问题的本质是系统资源已达瓶颈但请求处理不够快…...

用Rust还是JavaScript?Tauri 2.0系统托盘开发的两种姿势与选型建议

Tauri 2.0系统托盘开发&#xff1a;Rust与JavaScript的技术选型深度解析 当桌面应用需要常驻后台运行时&#xff0c;系统托盘功能便成为用户体验的关键组件。Tauri 2.0作为新一代跨平台桌面框架&#xff0c;允许开发者在前端JavaScript与后端Rust两种技术栈中实现这一功能。本文…...

ARMv8开发实战:Aarch64函数调用那些坑(含AAPCS64避坑指南)

ARMv8开发实战&#xff1a;Aarch64函数调用那些坑&#xff08;含AAPCS64避坑指南&#xff09; 在嵌入式开发和系统编程领域&#xff0c;ARMv8架构因其出色的能效比和性能表现&#xff0c;已经成为移动设备、服务器甚至超级计算机的主流选择。然而&#xff0c;当开发者从x86平台…...

利用快马平台AI能力,十分钟搭建你的Copilot式代码生成原型

今天想和大家分享一个快速验证AI编程助手&#xff08;Copilot类工具&#xff09;原型的实践。作为一个经常需要快速验证想法的开发者&#xff0c;我发现用InsCode(快马)平台可以省去很多搭建环境的时间&#xff0c;特别适合做这种概念验证。 明确核心需求 Copilot的核心能力其实…...

电子元器件检测数据集VOC+YOLO格式1032张5类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件)图片数量(jpg文件个数)&#xff1a;1032标注数量(xml文件个数)&#xff1a;1032标注数量(txt文件个数)&#xff1a;1032标注类别…...

OpenClaw故障排查大全:GLM-4.7-Flash接口超时与网关启动失败

OpenClaw故障排查大全&#xff1a;GLM-4.7-Flash接口超时与网关启动失败 1. 问题背景与典型症状 最近在本地部署OpenClaw对接GLM-4.7-Flash模型时&#xff0c;遇到了两个棘手问题&#xff1a;接口调用频繁超时和网关服务启动失败。作为一个习惯用技术解决实际问题的开发者&am…...

Kimi,Minimax教你的客服怎么做客服

Kimi&#xff0c;教你怎么做客服。下面是Kimi根据我提供的图片写的文章。不是说minimax全面领先kimi&#xff0c;至少我在不断的提高自己的kimi会员等级。但是有时候&#xff0c;这是被迫的消耗积分和额度。199的套餐也快消耗完了。消耗积分是应该的&#xff0c;关键是要用在刀…...

IDEA 2023.3 配置 JavaWeb 项目完整流程:从新建到打包 War 的保姆级避坑指南

IDEA 2023.3 配置 JavaWeb 项目完整流程&#xff1a;从新建到打包 War 的保姆级避坑指南 作为一名长期使用 IntelliJ IDEA 进行 JavaWeb 开发的工程师&#xff0c;我深知在配置项目时可能遇到的各种"坑"。特别是对于刚接触 IDEA 的新手来说&#xff0c;从项目创建到最…...