Chapter 14 — 命令模式 Command灵魂速记把请求打包成对象——想存就存想撤就撤想排队就排队。秒懂类比去餐厅吃饭你告诉服务员“来一份宫保鸡丁”服务员把你的要求写在小票上小票传给厨师厨师照着小票做菜小票就是命令对象。它把谁要什么封装起来了。好处可以排队先来后到可以撤销划掉那道菜可以记录账单问题引入// 灾难现场按钮直接调用业务逻辑classButton{voidonClick(){document.save();// 按钮和业务逻辑紧耦合}};// 问题// 1. 同样的保存菜单栏也要调、快捷键也要调——到处重复// 2. 想加撤销在每个调用点都加// 3. 想做宏录制记录哪些操作模式结构┌──────────┐ ┌──────────┐ ┌──────────┐ │ Invoker │─────→│ Command │────→│ Receiver │ │ (服务员) │ 持有 │ (小票) │ 知道│ (厨师) │ ├──────────┤ ├──────────┤ ├──────────┤ │ invoke()│ │execute()│ │action() │ │ │ │undo() │ │ │ └──────────┘ └──────────┘ └──────────┘四个角色Command命令接口executeundoConcreteCommand具体命令知道找谁干活Receiver真正干活的Invoker触发命令的按钮、菜单、快捷键……C 实现#includeiostream#includememory#includestack#includestring#includevector// Receiver文本编辑器 classTextEditor{public:voidinsertText(size_t pos,conststd::stringtext){content_.insert(pos,text);std::cout [插入] \text\ at pos\n;}voiddeleteText(size_t pos,size_t length){autodeletedcontent_.substr(pos,length);content_.erase(pos,length);std::cout [删除] \deleted\ at pos\n;}conststd::stringcontent()const{returncontent_;}private:std::string content_;};// Command 接口 classCommand{public:virtual~Command()default;virtualvoidexecute()0;virtualvoidundo()0;};// 具体命令插入文本 classInsertCommand:publicCommand{public:InsertCommand(TextEditoreditor,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:TextEditoreditor_;size_t pos_;std::string text_;};// 具体命令删除文本 classDeleteCommand:publicCommand{public:DeleteCommand(TextEditoreditor,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:TextEditoreditor_;size_t pos_;size_t length_;std::string deletedText_;// 备份用于撤销};// Invoker命令管理器支持撤销/重做 classCommandManager{public:voidexecute(std::unique_ptrCommandcmd){cmd-execute();undoStack_.push(std::move(cmd));// 执行新命令后清空重做栈while(!redoStack_.empty())redoStack_.pop();}voidundo(){if(undoStack_.empty()){std::cout (没有可撤销的操作)\n;return;}std::cout ↩️ 撤销:\n;autocmdundoStack_.top();cmd-undo();redoStack_.push(std::move(cmd));undoStack_.pop();}voidredo(){if(redoStack_.empty()){std::cout (没有可重做的操作)\n;return;}std::cout ↪️ 重做:\n;autocmdredoStack_.top();cmd-execute();undoStack_.push(std::move(cmd));redoStack_.pop();}private:std::stackstd::unique_ptrCommandundoStack_;std::stackstd::unique_ptrCommandredoStack_;};intmain(){TextEditor editor;CommandManager manager;// 输入 Hello World!manager.execute(std::make_uniqueInsertCommand(editor,0,Hello));std::cout 内容: \editor.content()\\n\n;manager.execute(std::make_uniqueInsertCommand(editor,5, World!));std::cout 内容: \editor.content()\\n\n;// 删除 Worldmanager.execute(std::make_uniqueDeleteCommand(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 StrategyCommandStrategy目的封装请求/操作封装算法核心动词“做这件事”“用这种方式做”可撤销✅ 天然支持 undo❌ 一般不支持存储经常入队列/入栈运行时替换类比小票记录要做什么导航策略走哪条路一句话分清Command 是名词一个请求Strategy 是副词用什么方式。Command vs MementoCommandMemento撤销方式反向操作insert→delete恢复快照整体还原存储什么操作本身对象的状态适用操作可逆时操作不可逆、或状态复杂时现代 C 小贴士简单场景下std::function就是天然的命令对象#includefunctional#includevectorstd::vectorstd::functionvoid()commandQueue;// 入队commandQueue.push_back([editor]{editor.insertText(0,Hi);});commandQueue.push_back([editor]{editor.deleteText(0,2);});// 批量执行for(autocmd:commandQueue)cmd();但如果需要 undo还是老老实实用类。