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

命令模式:在复杂业务中解耦“屎山”代码的架构实践

在 Java 开发中命令模式Command Pattern的核心价值在于解耦请求发送者Invoker与请求接收者Receiver并将请求封装为对象。这使得我们可以轻松实现撤销/重做、事务日志、宏命令组合命令、异步处理、流量削峰等高级功能。在经典的DAO - Service - Controller三层架构中命令模式通常作为Service 层的增强设计或独立的任务调度层存在用于处理那些“操作复杂、需要事务控制、可能需要重试或审计”的业务场景。所以本文主要案例实践剖析命令模式Command Pattern如何应用复杂业务已解决“屎山”堆砌带来的技术负债。一、核心设计理念在三层架构中的位置层级传统做法引入命令模式后的做法Controller直接调用service.doSomething()创建Command对象调用commandExecutor.execute(command)Service包含大量if-else分支处理不同业务抽象出Command接口每个具体业务逻辑成为一个独立的 Command 类Service 退化为命令的执行器或协调者DAO被 Service 调用保持不变被 Command 内部调用以完成数据持久化设计图解ControllerhandleRequest()CommandExecutorexecute(Command cmd)undo(Command cmd)«interface»Commandexecute()undo()ConcreteCommandA-receiver: ServiceAexecute()undo()ServiceAdoBusinessLogic()DAOsave()update()二、复杂业务案例银行核心系统的“分布式转账与冲正”系统1. 业务背景在银行或支付系统中转账不仅仅是“扣钱 加钱”。它涉及多重校验余额、风控、黑名单、限额。多系统交互核心账务系统、积分系统、通知系统、反洗钱系统。高可靠性要求必须支持原子性要么全成功要么全失败。异常恢复如果第二步失败必须自动执行**冲正Undo**操作回滚第一步。审计留痕所有操作必须记录日志支持人工或自动重放。这是一个典型的适合使用命令模式的场景因为我们需要将“转账”这个动作封装成一个对象以便在执行失败时能方便地调用其undo()方法或者将其存入数据库队列进行异步重试。2. 架构设计(1) 定义命令接口 (Command)这是模式的核心定义执行和撤销操作。publicinterfaceCommand{/** * 执行命令 * throws BusinessException 业务异常时抛出触发回滚 */voidexecute()throwsBusinessException;/** * 撤销命令冲正 * 当 execute 失败时调用或者用于人工冲正 */voidundo();/** * 获取命令的唯一标识用于日志追踪 */StringgetTraceId();/** * 获取命令类型用于审计 */StringgetCommandType();}(2) 具体命令实现 (ConcreteCommand)以“跨行转账”为例该命令封装了扣款、入账、发通知等逻辑并定义了如何回滚。ComponentScope(prototype)// 重要命令对象通常是有状态的需原型模式publicclassTransferCommandimplementsCommand{privatefinalStringtraceId;privatefinalLongfromAccountId;privatefinalLongtoAccountId;privatefinalBigDecimalamount;// 依赖注入业务服务ReceiverAutowiredprivateAccountServiceaccountService;AutowiredprivateNotificationServicenotificationService;AutowiredprivateRiskControlServiceriskControlService;// 状态标记记录哪些步骤已执行以便精准回滚privatebooleandebitExecutedfalse;privatebooleancreditExecutedfalse;privatebooleannotifiedfalse;publicTransferCommand(StringtraceId,LongfromAccountId,LongtoAccountId,BigDecimalamount){this.traceIdtraceId;this.fromAccountIdfromAccountId;this.toAccountIdtoAccountId;this.amountamount;}Overridepublicvoidexecute()throwsBusinessException{try{// 1. 风控校验riskControlService.check(traceId,fromAccountId,amount);// 2. 扣款 (付款方)accountService.debit(fromAccountId,amount);debitExecutedtrue;// 3. 入账 (收款方)accountService.credit(toAccountId,amount);creditExecutedtrue;// 4. 发送通知notificationService.sendTransferSuccess(fromAccountId,toAccountId,amount);notifiedtrue;System.out.println([命令] 转账成功: traceId);}catch(Exceptione){// 发生任何异常立即触发本地回滚补偿// 注意这里通常配合外层的事务管理器或手动补偿机制undo();thrownewBusinessException(转账执行失败已触发冲正,e);}}Overridepublicvoidundo(){System.out.println([命令] 开始冲正操作: traceId);// 逆序回滚已执行的步骤if(notified){// 通知无法物理撤回但可以发送一条“交易取消”的通知notificationService.sendTransferCancelled(fromAccountId,toAccountId,amount);}if(creditExecuted){// 回滚入账try{accountService.reverseCredit(toAccountId,amount);System.out.println([命令] 已回滚入账);}catch(Exceptione){// 记录严重错误可能需要人工介入System.err.println([命令] 回滚入账失败需人工干预: e.getMessage());}}if(debitExecuted){// 回滚扣款try{accountService.reverseDebit(fromAccountId,amount);System.out.println([命令] 已回滚扣款);}catch(Exceptione){System.err.println([命令] 回滚扣款失败需人工干预: e.getMessage());}}}OverridepublicStringgetTraceId(){returntraceId;}OverridepublicStringgetCommandType(){returnTRANSFER;}}(3) 命令执行器 (CommandExecutor)这是 Invoker负责管理命令的生命周期、事务边界、日志记录和重试。ServicepublicclassCommandExecutor{AutowiredprivateCommandLogDaocommandLogDao;// 持久化命令日志/** * 同步执行命令带事务和日志 */Transactional(rollbackForException.class)publicvoidexecuteSync(Commandcommand){// 1. 记录命令开始日志logCommand(command,STARTED);try{// 2. 执行业务逻辑command.execute();// 3. 记录成功日志logCommand(command,SUCCESS);}catch(BusinessExceptione){// 4. 记录失败日志undo 已在 command 内部调用或在此处统一调用logCommand(command,FAILED: e.getMessage());// 抛出异常让 Spring 事务回滚数据库操作throwe;}}/** * 异步执行命令放入队列用于削峰或解耦 */publicvoidexecuteAsync(Commandcommand){// 先持久化命令状态为 PENDINGlogCommand(command,PENDING);// 提交到线程池或消息队列CompletableFuture.runAsync(()-{try{// 在异步线程中开启新事务executeSync(command);}catch(Exceptione){// 异步失败处理加入重试队列或报警handleAsyncFailure(command,e);}});}privatevoidlogCommand(Commandcmd,Stringstatus){CommandLoglognewCommandLog();log.setTraceId(cmd.getTraceId());log.setType(cmd.getCommandType());log.setStatus(status);log.setCreateTime(LocalDateTime.now());commandLogDao.insert(log);}privatevoidhandleAsyncFailure(Commandcmd,Exceptione){// 策略加入死信队列等待人工处理或定时任务重试System.err.println(异步命令失败进入死信队列: cmd.getTraceId());}}(4) Controller 层调用Controller 变得非常轻量只负责构建命令并委托给执行器。RestControllerRequestMapping(/api/transfer)publicclassTransferController{AutowiredprivateCommandExecutorcommandExecutor;// 工厂或服务用于创建命令对象AutowiredprivateCommandFactorycommandFactory;PostMappingpublicResultVoidtransfer(RequestBodyTransferDTOdto){StringtraceIdUUID.randomUUID().toString();// 1. 创建具体的命令对象CommandcommandcommandFactory.createCommand(TRANSFER,traceId,dto);// 2. 委托执行器执行// 可以选择同步或异步commandExecutor.executeSync(command);returnResult.success(交易已受理流水号traceId);}}三、这种设计的核心优势1. 完美的“撤销/冲正”机制在传统 Service 写法中如果第 3 步失败你需要在第 3 步的catch块里写代码去调用第 2 步和第 1 步的回滚方法代码耦合且容易遗漏。命令模式将“执行”和“撤销”封装在同一个对象内。execute()失败自动调用undo()逻辑内聚且undo()可以设计得非常健壮如记录每一步的状态标志位。2. 支持宏命令组合模式如果业务变成“批量转账”你可以创建一个MacroCommand里面包含 100 个TransferCommand。publicclassMacroCommandimplementsCommand{privateListCommandcommandsnewArrayList();// addCommand...publicvoidexecute(){for(Commandc:commands)c.execute();}publicvoidundo(){// 逆序撤销for(inticommands.size()-1;i0;i--)commands.get(i).undo();}}这对财务日终批处理非常有用。3. 易于实现“命令日志”和“故障恢复”由于命令是对象你可以轻松地将命令对象序列化JSON并存入数据库。场景系统宕机重启后扫描数据库中状态为PENDING或FAILED的命令记录反序列化成对象重新调用execute()。这是实现最终一致性和可靠消息投递的经典手段。4. 解耦与扩展Controller不需要知道具体的业务逻辑细节只需要知道有个CommandExecutor。新增一种业务如“理财购买”只需新增一个BuyWealthCommand类无需修改现有的 Controller 或 Executor 代码符合开闭原则。5. 流量削峰通过executeAsync方法可以将突发的交易请求放入内存队列或数据库队列后端消费者按自己的能力匀速处理保护数据库不被打挂。四、与策略模式的对比特性命令模式 (Command)策略模式 (Strategy)意图封装请求支持撤销、日志、队列封装算法支持运行时切换算法核心方法execute(),undo()execute()(通常无 undo)状态命令对象通常持有请求参数和执行状态策略对象通常无状态或仅持有配置生命周期一次请求对应一个命令实例 (Prototype)策略实例通常单例被多次复用适用场景转账、订单创建、任务调度、宏操作支付方式选择、排序算法、折扣计算在上面的银行案例中因为需要记录“谁在什么时候转了多少钱”以及“失败了怎么退回去”所以必须用命令模式。如果是单纯的“根据用户等级计算手续费”则用策略模式。五、总结在 DAO-Service-Controller 架构中引入命令模式位置在 Service 层之上构建一层“命令层”或在 Service 内部使用命令对象编排复杂逻辑。核心价值解决长流程事务、补偿机制Undo、操作审计和异步解耦问题。适用场景金融交易、电商下单、工作流引擎、复杂的表单提交等对数据一致性和可追溯性要求极高的业务。通过这种设计你的系统将从简单的“增删改查”进化为具备自愈能力和高可维护性的企业级应用。

相关文章:

命令模式:在复杂业务中解耦“屎山”代码的架构实践

在 Java 开发中,命令模式(Command Pattern) 的核心价值在于解耦请求发送者(Invoker)与请求接收者(Receiver),并将请求封装为对象。这使得我们可以轻松实现撤销/重做、事务日志、宏命…...

基于LLM的Agent构建核心策略全解(非常详细),从理论到实战,收藏这一篇就够了!

基于 LLM 的 Agent 构建核心遵循 「极简优先、能力分层、流程可控、治理闭环」 四大原则,以 LLM 为智能核心,通过工具增强、流程编排、协作规范、安全治理四层能力搭建,从「单点任务执行」逐步升级为「复杂任务自治 / 协作」,同时…...

py读取dat/plt

import numpy as np import matplotlib.pyplot as plt import re# # 1. 解析函数 # def parse_tecplot_file(filepath):"""解析TECPLOT BLOCK格式数据文件参数:filepath: 文件路径返回:data_dict: 数据字典header_info: 头部信息"""with open(f…...

GraphRAG 为什么比传统 RAG 准? 从分块检索到知识图谱增强的工程实践

如果你在企业里落地过 RAG 系统,大概率踩过这个坑:知识库里明明有答案,但 AI 给的要么不完整,要么牛头不对马嘴。根本原因不是模型不够强,而是传统分块检索天然有信息断裂的问题。这篇文章讲清楚这件事的来龙去脉&…...

OWL ADVENTURE在教育培训中的应用:让AI学习更有趣

OWL ADVENTURE在教育培训中的应用:让AI学习更有趣 1. 引言:当AI教育遇见像素艺术 想象一下这样的场景:一群小学生围坐在电脑前,不是在玩游戏,而是在通过一个像素风格的界面与AI进行互动学习。他们上传自己画的涂鸦&a…...

PasteMD实战:3个真实场景手把手教你美化杂乱文本

PasteMD实战:3个真实场景手把手教你美化杂乱文本 1. 为什么你需要PasteMD 在日常工作中,我们经常遇到这些令人头疼的场景: 从会议录音转录的笔记杂乱无章,重要信息淹没在大量口语化表达中复制粘贴的代码片段丢失了原有的格式和…...

高质量AI论文平台推荐,具备智能降重和自然改写能力,帮助规避查重风险

开头总结工具对比(技能4) �� 为帮助学生们快速选出最适合的AI论文工具,我从处理速度、降重效果和核心优势三个维度,对比了6款热门网站,数据基于实际使用案例: 工具名称 处理速度 降…...

WiFiEsp库深度解析:AT模式下ESP8266与Arduino的可靠WiFi驱动

1. WiFiEsp 库深度技术解析:面向嵌入式工程师的 AT 模式 ESP8266 驱动实践指南1.1 工程定位与设计哲学WiFiEsp 是一个面向 Arduino 生态的AT 命令桥接型 WiFi 驱动库,其核心价值不在于替代 ESP8266 的原生 SDK 开发,而在于为传统 MCU&#xf…...

感应电机异步电机定子匝间短路的仿真研究基于Matlab Simulink平台

感应电机 异步电机定子匝间短路仿真 matlab simulink啪嗒一声按下启动键,车间里那台老旧的异步电机突然发出刺耳的蜂鸣声。作为设备维护的老油条,我抄起万用表就往定子绕组上怼——果然,又是该死的匝间短路在作妖。这玩意就像电机的心脏早搏&…...

解决音频延迟与设备冲突:FlexASIO通用驱动配置指南

解决音频延迟与设备冲突:FlexASIO通用驱动配置指南 【免费下载链接】FlexASIO A flexible universal ASIO driver that uses the PortAudio sound I/O library. Supports WASAPI (shared and exclusive), KS, DirectSound and MME. 项目地址: https://gitcode.com…...

终极指南:如何在2分钟内通过PowerShell一键安装Windows包管理器Winget

终极指南:如何在2分钟内通过PowerShell一键安装Windows包管理器Winget 【免费下载链接】winget-install Install winget tool using PowerShell! Prerequisites automatically installed. Works on Windows 10/11 and Server 2022. 项目地址: https://gitcode.com…...

求职招聘小程序平台运营版源码系统-含全功能PHP后台+完整的搭建教程

求职者服务功能视频招聘专区:设有专门的视频招聘板块,求职者可在此浏览企业发布的招聘视频,直观了解企业的工作环境、企业文化等信息,同时也能上传自己的视频简历,增加求职亮点。精准职位搜索:支持求职者通…...

JavaScript基础课程十八、异步编程高级(async/await + 模块化)

本课聚焦前端异步编程终极方案async/await与ES6模块化,是异步编程的收尾与进阶内容。async/await依托Promise,用同步写法实现异步逻辑,彻底解决回调嵌套和链式调用繁琐问题,是当前项目主流异步写法。模块化则解决代码混乱、全局污…...

【大模型RAG02】HyDE 精读

HyDE 精读笔记 Precise Zero-Shot Dense Retrieval without Relevance Labels 一、论文基本信息 论文标题:Precise Zero-Shot Dense Retrieval without Relevance Labels(无需相关标签的精准零样本密集检索) 作者:Luyu Gao, Xueguang Ma, Jimmy Lin, Jamie Callan(卡内基…...

AIGlasses OS Pro 实战:AIGC内容创作中的视觉元素合规性审核

AIGlasses OS Pro 实战:AIGC内容创作中的视觉元素合规性审核 最近和几个做AIGC平台的朋友聊天,他们都在头疼同一个问题:用户每天生成的海量图片和视频,怎么才能又快又准地筛出那些不合规的内容?人工审核团队已经三班倒…...

稳定性平台—版本接维

稳定性平台—版本接维...

DeepSeek-OCR-2部署案例:K8s集群中水平扩展OCR微服务实践

DeepSeek-OCR-2部署案例:K8s集群中水平扩展OCR微服务实践 1. 项目背景与价值 DeepSeek-OCR-2是DeepSeek团队推出的新一代OCR识别模型,采用创新的DeepEncoder V2技术,能够智能理解图像内容并动态重组识别区域,彻底改变了传统OCR从…...

50. 随机数排序

50. 随机数排序 题目描述 生成 N 个 1 到 500 的随机数,你需要删除掉其中重复的数字,即相同的数字只保留一个,把其余相同的数字去掉,然后再把这些数从小到大进行输出。 输入描述 第一行先输入随机整数的个数 N 。 接下来一行包含 …...

vue和nuxt的整合项目报错【Vue warn】: The client-side rendered virtual DOM tree is....并且页面的生命周期函数执行两次,彻底解决方案!

问题描述:当我在做一个查询课程详情的功能时候,想顺便在后台修改课程的浏览量,即让它加1,但发现每次刷新页面,数值竟然增加两次!于是我在这个页面的生命周期函数里面打印东西,发现每次打印的都是…...

如何用Java构建企业级电商聊天系统:MallChat架构深度解析

如何用Java构建企业级电商聊天系统:MallChat架构深度解析 【免费下载链接】MallChat mallchat的后端项目,是一个既能购物又能聊天的电商系统。以互联网企业级开发规范的要求来实现它,电商该有的购物车,订单,支付&#…...

轴承3d模型为什么 WL 迭代在 3 次后就收敛了

很好的问题!让我解释一下为什么 WL 迭代在 3 次后就收敛了。 WL (Weisfeiler-Lehman) 图核收敛原理 核心机制 WL 算法通过迭代细化节点标签来捕获图的结构信息: 迭代 0: 使用初始标签(这里是面类型:圆柱面、圆环面、平面&#xff…...

Windows查看快捷键占用情况

文章目录问题描述解决方案常用快捷键参考文献问题描述 IDE 使用快捷键没反应,却找不出来是哪个程序占用了快捷键(热键),通常是 QQ、网易云音乐等软件的问题。 解决方案 使用 OpenArk,根据文档描述,它可以…...

wl迭代以哪个节点为最后零件点进行计算呢,怎么实现图不变性的

好问题!让我详细解释 WL 迭代的计算机制和图不变性的实现原理。 已查看文件 face_graph_builder.cs 非常好的问题!让我详细解释 WL 迭代的计算机制和图不变性原理: 🔍 WL 迭代的核心机制 ❌ 没有"最后节点"的概念 WL 算…...

mysql数据库的4中隔离级别详解

众所周知MySQL从5.5.8开始,Innodb就是默认的存储引擎,Innodb最大的特点是:支持事务、支持行级锁。 既然支持事务,那么就会有处理并发事务带来的问题:更新丢失、脏读、不可重复读、幻读;相应的为了解决这四个…...

java8特性Stream 常见用法

最近在开发中看到同事使用stream 进行集合的数据转换List<object> ->List<String> List<object> --> Map<Long, object>&#xff0c;感觉十分简单方便&#xff0c;上网找了一下资料学习一下 Java 8 Stream | 菜鸟教程 记录一下我们常见用法 …...

647836

6378452...

Pixel Dimension Fissioner实操手册:实时HP状态与引擎负载可视化监控

Pixel Dimension Fissioner实操手册&#xff1a;实时HP状态与引擎负载可视化监控 1. 工具概览 Pixel Dimension Fissioner是一款基于MT5-Zero-Shot-Augment核心引擎构建的文本增强工具&#xff0c;采用独特的16-bit像素冒险风格界面设计。与传统AI工具不同&#xff0c;它将文…...

matlab基于CNN卷积神经网络的人脸表情情绪识别项目课题,采用GUI界面

matlab基于CNN卷积神经网络的人脸表情情绪识别项目课题&#xff0c;采用GUI界面&#xff0c; 【包括】 matlab源码&#xff0c;可以设置网络结构&#xff0c;训练方式等数据集&#xff08;7类表情&#xff0c;200多张人脸图像&#xff09;GUI界面。可显示整个测试集的结果&…...

AppKit:嵌入式Linux C++应用开发框架

1. 项目概述AppKit 是一个面向嵌入式 Linux 平台的 C14 应用开发框架&#xff0c;其设计目标明确指向两个核心工程诉求&#xff1a;提升应用层开发效率与增强运行时健壮性。在资源受限、实时性要求严苛、长期稳定运行成为刚需的嵌入式 Linux 场景中&#xff0c;开发者常面临重复…...

如何使用 Gherkin 解析器:Behat 测试的终极指南

如何使用 Gherkin 解析器&#xff1a;Behat 测试的终极指南 【免费下载链接】Gherkin Gherkin parser, written in PHP for Behat project 项目地址: https://gitcode.com/gh_mirrors/gh/Gherkin Gherkin 解析器是 Behat 项目的核心组件&#xff0c;它提供了一种简单而强…...