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

新谈设计模式 Chapter 14 — 命令模式 Command

Chapter 14 — 命令模式 Command

灵魂速记:把请求打包成对象——想存就存,想撤就撤,想排队就排队。


秒懂类比

去餐厅吃饭:

  1. 你告诉服务员:“来一份宫保鸡丁”
  2. 服务员把你的要求写在小票
  3. 小票传给厨师
  4. 厨师照着小票做菜

小票就是命令对象。它把"谁要什么"封装起来了。好处:

  • 可以排队(先来后到)
  • 可以撤销(划掉那道菜)
  • 可以记录(账单)

问题引入

// 灾难现场:按钮直接调用业务逻辑classButton{voidonClick(){document.save();// 按钮和业务逻辑紧耦合}};// 问题:// 1. 同样的"保存",菜单栏也要调、快捷键也要调——到处重复// 2. 想加撤销?在每个调用点都加?// 3. 想做宏录制?记录哪些操作?

模式结构

┌──────────┐ ┌──────────┐ ┌──────────┐ │ Invoker │─────→│ Command │────→│ Receiver │ │ (服务员) │ 持有 │ (小票) │ 知道│ (厨师) │ ├──────────┤ ├──────────┤ ├──────────┤ │ +invoke()│ │+execute()│ │+action() │ │ │ │+undo() │ │ │ └──────────┘ └──────────┘ └──────────┘

四个角色

  • Command:命令接口(execute+undo
  • ConcreteCommand:具体命令,知道找谁干活
  • Receiver:真正干活的
  • Invoker:触发命令的(按钮、菜单、快捷键……)

C++ 实现

#include<iostream>#include<memory>#include<stack>#include<string>#include<vector>// ========== Receiver:文本编辑器 ==========classTextEditor{public:voidinsertText(size_t pos,conststd::string&text){content_.insert(pos,text);std::cout<<" [插入] \""<<text<<"\" at "<<pos<<"\n";}voiddeleteText(size_t pos,size_t length){autodeleted=content_.substr(pos,length);content_.erase(pos,length);std::cout<<" [删除] \""<<deleted<<"\" at "<<pos<<"\n";}conststd::string&content()const{returncontent_;}private:std::string content_;};// ========== Command 接口 ==========classCommand{public:virtual~Command()=default;virtualvoidexecute()=0;virtualvoidundo()=0;};// ========== 具体命令:插入文本 ==========classInsertCommand:publicCommand{public:InsertCommand(TextEditor&editor,size_t pos,std::string text):editor_(editor),pos_(pos),text_(std::move(text)){}voidexecute()override{editor_.insertText(pos_,text_);}voidundo()override{editor_.deleteText(pos_,text_.size());}private:TextEditor&editor_;size_t pos_;std::string text_;};// ========== 具体命令:删除文本 ==========classDeleteCommand:publicCommand{public:DeleteCommand(TextEditor&editor,size_t pos,size_t length):editor_(editor),pos_(pos),length_(length){}voidexecute()override{deletedText_=editor_.content().substr(pos_,length_);// 先备份editor_.deleteText(pos_,length_);}voidundo()override{editor_.insertText(pos_,deletedText_);// 还原}private:TextEditor&editor_;size_t pos_;size_t length_;std::string deletedText_;// 备份,用于撤销};// ========== Invoker:命令管理器(支持撤销/重做) ==========classCommandManager{public:voidexecute(std::unique_ptr<Command>cmd){cmd->execute();undoStack_.push(std::move(cmd));// 执行新命令后清空重做栈while(!redoStack_.empty())redoStack_.pop();}voidundo(){if(undoStack_.empty()){std::cout<<" (没有可撤销的操作)\n";return;}std::cout<<" ↩️ 撤销:\n";auto&cmd=undoStack_.top();cmd->undo();redoStack_.push(std::move(cmd));undoStack_.pop();}voidredo(){if(redoStack_.empty()){std::cout<<" (没有可重做的操作)\n";return;}std::cout<<" ↪️ 重做:\n";auto&cmd=redoStack_.top();cmd->execute();undoStack_.push(std::move(cmd));redoStack_.pop();}private:std::stack<std::unique_ptr<Command>>undoStack_;std::stack<std::unique_ptr<Command>>redoStack_;};intmain(){TextEditor editor;CommandManager manager;// 输入 "Hello World!"manager.execute(std::make_unique<InsertCommand>(editor,0,"Hello"));std::cout<<" 内容: \""<<editor.content()<<"\"\n\n";manager.execute(std::make_unique<InsertCommand>(editor,5," World!"));std::cout<<" 内容: \""<<editor.content()<<"\"\n\n";// 删除 "World"manager.execute(std::make_unique<DeleteCommand>(editor,6,5));std::cout<<" 内容: \""<<editor.content()<<"\"\n\n";// 撤销!manager.undo();std::cout<<" 内容: \""<<editor.content()<<"\"\n\n";// 再撤销!manager.undo();std::cout<<" 内容: \""<<editor.content()<<"\"\n\n";// 重做!manager.redo();std::cout<<" 内容: \""<<editor.content()<<"\"\n";}

输出:

[插入] "Hello" at 0 内容: "Hello" [插入] " World!" at 5 内容: "Hello World!" [删除] "World" at 6 内容: "Hello !" ↩️ 撤销: [插入] "World" at 6 内容: "Hello World!" ↩️ 撤销: [删除] " World!" at 5 内容: "Hello" ↪️ 重做: [插入] " World!" at 5 内容: "Hello World!"

什么时候用?

✅ 适合❌ 别用
需要撤销/重做操作简单且不需要撤销
需要排队/延迟执行操作直接调用就行
需要记录操作日志不需要审计追踪
需要宏命令(组合多个操作)操作之间无关联
GUI 按钮/菜单/快捷键解耦只有一个调用入口

防混淆

Command vs Strategy

CommandStrategy
目的封装请求/操作封装算法
核心动词“做这件事”“用这种方式做”
可撤销?✅ 天然支持 undo❌ 一般不支持
存储经常入队列/入栈运行时替换
类比小票(记录要做什么)导航策略(走哪条路)

一句话分清:Command 是名词(一个请求),Strategy 是副词(用什么方式)。

Command vs Memento

CommandMemento
撤销方式反向操作(insert→delete)恢复快照(整体还原)
存储什么操作本身对象的状态
适用操作可逆时操作不可逆、或状态复杂时

现代 C++ 小贴士

简单场景下,std::function就是天然的命令对象:

#include<functional>#include<vector>std::vector<std::function<void()>>commandQueue;// 入队commandQueue.push_back([&editor]{editor.insertText(0,"Hi");});commandQueue.push_back([&editor]{editor.deleteText(0,2);});// 批量执行for(auto&cmd:commandQueue)cmd();

但如果需要 undo,还是老老实实用类。

http://www.jsqmd.com/news/653623/

相关文章:

  • HLS.js直播优化实战:从推流到播放,如何将延迟控制在5秒内?
  • Transformers库分析
  • 终极指南:Chrono 自然语言日期解析器的 Jest 代码覆盖率配置与报告分析
  • Snarkdown 性能优化实战:为什么它比传统解析器更快
  • 2026年口碑好的不锈钢平移门/钢制平移门厂家哪家好 - 行业平台推荐
  • CodeSearchNet代码解析器深入剖析:函数提取与注释处理原理
  • 题解:洛谷 P1006 [NOIP 2008 提高组] 传纸条
  • 别再手动格式化JSON了!用vue-json-viewer三行代码搞定高亮、折叠与复制
  • WAN2.2-文生视频+SDXL_Prompt风格实战手册:视频BGM自动匹配与音画同步方案
  • 【乳腺癌分类】图像处理技术和卷积神经网络早发乳腺癌分类【含Matlab源码 15333期】
  • ArcGIS Desktop标注实战:从自动标注到手动微调注记的完整避坑指南
  • 个性化设置:让用户定制自己的 Agent
  • 小红书商品笔记抓取:笔记ID与商品关联关系解析
  • Kaneo Docker部署教程:从本地开发到生产环境的完整方案
  • 签证时效、暴雨预警、小众民宿库存——AI旅游攻略如何实时联动27类动态因子?SITS2026技术委员会独家拆解
  • 5种WaveNet vocoder输出分布对比:MoL vs 高斯 vs μ-law量化
  • 终极指南:如何在 NestJS 中集成 Chrono 实现智能日期解析
  • 生成式AI错误日志形同虚设?教你用LangChain+Prometheus+自定义Error Schema实现错误可追溯、可归因、可复现
  • 2026奇点大会技术白皮书节选(机密级):AI简历优化器的对抗样本防御机制与反偏见训练日志(含真实A/B测试数据集)
  • LOD和UV光照贴图管理:Blender For Unreal Engine优化工作流程
  • 解锁WPS甘特图:从零搭建高效项目进度管理模板
  • 免费论文AIGC率检测工具推荐 学术场景直接可用
  • Python的__new__中的缓存弱引用
  • M920x黑苹果实战指南:从硬件解锁到完美macOS体验
  • 什么是“荒加工”(机械加工方向)
  • 从Prompt到出版级叙事,SITS2026演讲实录:7步构建可商用AI故事工作流
  • # 发散创新:用 Rust实现高性能物理引擎的底层架构设计与实战在游戏开发、虚拟仿真和机器人控
  • GPU推理优化教程:提升Local AI MusicGen生成速度
  • 纵向磨削和径向磨削
  • BongoCat终极指南:让可爱猫咪为你的输入操作增添无限乐趣