资讯详情

14-A-命令模式详解

📅 2026/10/18 1:56:28 | 华诺云谱 👁 阅读
14-A-命令模式详解
14-A-命令模式详解关键词命令模式、请求对象化、任务队列、撤销重做、操作日志回放、Runnable命令模式解决一个问题把一个请求/操作变成对象从而可以存储、排队、记录、撤销、重放。导出任务排队执行、订单操作可回放、编辑器撤销重做——本质都是请求对象化之后获得的能力。 术语速查表术语人话解释命令Command封装了做什么 用什么数据做的对象调用者Invoker接收命令并决定何时执行的角色队列/调度器/按钮接收者Receiver真正干活的对象业务服务撤销undo命令对象记录反向操作支持回滚宏命令一组命令的组合组合模式 命令模式一、不用命令模式的问题后台运营操作台要支持导出报表、批量改价、发送优惠券、刷新缓存。直接写法// ❌ Invoker 直接耦合每个操作publicvoidexecute(Stringaction,MapString,Objectparams){switch(action){caseEXPORT:reportService.export(...);break;caseREPRICE:priceService.batchUpdate(...);break;caseSEND_COUPON:couponService.send(...);break;// 每加一个操作改一次这里想排队想记录想撤销没门}}痛点操作无法排队都是即时调用、无法记录参数散在方法调用里、无法撤销调用完就没了、新增操作要改调度代码。二、模式结构提交execute委托落库客户端组装命令对象Invoker队列 / 调度器Command 对象携带参数Receiver业务服务操作日志可回放 / 可撤销关键转变调用方不再直接调用方法而是构造一个命令对象交给 Invoker——请求从行为变成了数据数据就能存、能传、能排队、能重放。三、业务场景运营操作台的任务队列命令接口publicinterfaceOpsCommandextendsSerializable{/** 操作类型用于日志展示和权限校验 */Stringtype();/** 执行 */CommandResultexecute();/** 撤销默认不支持可撤销的命令覆写 */defaultCommandResultundo(){thrownewUnsupportedOperationException(type() 不支持撤销);}/** 命令描述进操作日志人话 */Stringdescribe();}具体命令批量改价可撤销publicclassBatchRepriceCommandimplementsOpsCommand{privatefinaltransientPriceServicepriceService;// ReceiverprivatefinalMapLong,BigDecimalnewPrices;// 参数即数据privateMapLong,BigDecimaloldPrices;// 执行时快照供撤销publicBatchRepriceCommand(PriceServicepriceService,MapLong,BigDecimalnewPrices){this.priceServicepriceService;this.newPricesnewPrices;}OverridepublicStringtype(){returnBATCH_REPRICE;}OverridepublicCommandResultexecute(){// (1) 先快照旧价格——撤销的原料oldPricespriceService.batchQueryPrice(newPrices.keySet());// (2) 执行改价intaffectedpriceService.batchUpdate(newPrices);returnCommandResult.success(改价 affected 个商品);}OverridepublicCommandResultundo(){if(oldPricesnull){returnCommandResult.fail(未执行过无法撤销);}priceService.batchUpdate(oldPrices);// 用快照回滚returnCommandResult.success(已撤销改价);}OverridepublicStringdescribe(){return批量改价 newPrices.size() 个商品;}}具体命令导出报表不可撤销重任务publicclassExportReportCommandimplementsOpsCommand{privatefinaltransientReportServicereportService;privatefinalReportQueryquery;OverridepublicStringtype(){returnEXPORT_REPORT;}OverridepublicCommandResultexecute(){StringfileUrlreportService.export(query);// 耗时几分钟returnCommandResult.success(导出完成: fileUrl);}OverridepublicStringdescribe(){return导出报表 query.getReportName() (query.getDateRange());}}Invoker排队 记录 撤销ComponentpublicclassOpsCommandInvoker{privatefinalBlockingQueueWrappedCommandqueuenewLinkedBlockingQueue(1000);privatefinalOpsLogDaoopsLogDao;/** 提交命令不立即执行——落库 入队 */publicLongsubmit(OpsCommandcmd,Stringoperator){// (1) 记录操作日志命令对象自带 describe日志不用调用方拼LonglogIdopsLogDao.insert(newOpsLog(operator,cmd.type(),cmd.describe(),QUEUED,LocalDateTime.now()));// (2) 入队由后台线程按序执行queue.offer(newWrappedCommand(logId,cmd,operator));returnlogId;}/** 后台消费线程串行执行天然限流 */PostConstructpublicvoidstartWorker(){ThreadworkernewThread(()-{while(true){WrappedCommandwcqueue.take();try{CommandResultrwc.cmd().execute();opsLogDao.updateStatus(wc.logId(),r.isSuccess()?SUCCESS:FAILED,r.getMsg());}catch(Exceptione){opsLogDao.updateStatus(wc.logId(),FAILED,e.getMessage());}}},ops-command-worker);worker.setDaemon(true);worker.start();}/** 撤销从日志里找到命令重新执行 undo */publicCommandResultundo(LonglogId){OpsLoglogopsLogDao.find(logId);OpsCommandcmddeserialize(log.getPayload());returncmd.undo();}}调用方// 运营点批量改价按钮invoker.submit(newBatchRepriceCommand(priceService,Map.of(1001L,newBigDecimal(5999),1002L,newBigDecimal(6299))),operator_zhang);// 运营点导出——重任务排队执行页面立即返回任务号LongtaskIdinvoker.submit(newExportReportCommand(reportService,query),operator_li);// 改错了撤销invoker.undo(taskId);命令对象化之后免费获得的能力排队队列存命令、记录日志存命令序列化、撤销命令自带 undo、重放反序列化再 execute、延迟执行、事务组合宏命令。四、你天天在用的命令模式// Runnable 就是最简命令把要做的事对象化交给线程池Invoker排队执行executor.submit(()-reportService.export(query));// CompletableFuture、MQ 消息体、定时任务的 Job 对象——全是命令模式的变体MQ 消息就是分布式命令消息体 序列化的命令对象Broker 队列Consumer Invoker Receiver。理解了命令模式就理解了为什么消息体要自包含参数见 06-25-A-MQ通用问题与事务消息详解。五、框架中的同款实现框架应用说明JDKRunnable/Callable最简命令线程池是 InvokerJDKjavax.swing.Action按钮动作可复用、可禁用SpringTransactionCallbackTransactionTemplate把事务里要做的事对象化传入Quartz/XXL-JobJob/JobDetail定时命令Job 类 参数JobDataMapRocketMQ/Kafka消息体分布式命令Broker 排队Consumer 执行HystrixHystrixCommand命令 熔断/降级/排队一体化NettyChannelPipeline里传递的事件对象事件即命令六、常见误用与生产事故误用一所有方法调用都包成命令单个服务内部的普通方法调用包一层 Command类数量翻倍、没有任何排队/撤销/记录需求——纯过度设计。命令模式的价值来自请求要被存储/排队/撤销/重放这些需求没有这些需求就别包。误用二命令持有 Receiver 引用却要求可序列化// ❌ priceService 是 Spring Bean无法序列化——命令落库时 NotSerializableExceptionprivatefinalPriceServicepriceService;修复命令对象只存参数数据可序列化Receiver 在执行端通过 Spring 上下文或工厂重新获取publicCommandResultexecute(){PriceServicesvcSpringContextHolder.getBean(PriceService.class);// 执行端取...}事故撤销命令不幂等重复撤销把价格改乱了运营连点两次撤销undo()执行两遍——第二遍用的还是第一次的快照但期间另一个改价命令已经生效价格被回滚到了错误状态。修复撤销加版本号/状态机只有SUCCESS状态的日志能撤销撤销后状态变UNDO重复撤销直接拒绝撤销本身也要幂等带唯一撤销单号命令间有冲突时同一商品被两个改价命令操作撤销要校验该商品此后是否被其他命令修改过——被改过则拒绝自动撤销转人工。七、总结命令模式的核心请求对象化——把调用变成数据从而获得排队、记录、撤销、重放、延迟执行的能力。四角色Command做什么参数、Invoker何时做队列/调度、Receiver真正干活、Client组装命令。身边的命令模式Runnable 线程池、MQ 消息 Consumer、Job 调度器——理解它等于理解异步任务体系的骨架。生产纪律命令只存参数不存服务引用撤销必须幂等 状态机防重冲突命令的撤销要校验时序。代价类数量增加、命令序列化要考虑版本兼容——只在确实需要请求生命周期管理时使用。上一篇13-A-责任链模式详解下一篇15-A-解释器模式详解
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑