责任链模式让我的代码精简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&…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
