【深入浅出设计模式--命令模式】

2023-09-18 14:44:59

一、背景

  命令模式是一种行为设计模式,它可以将用户的命令请求转化为一个包含有相关参数信息的对象,命令的发送者不需要知道接收者是如何处理这条命令,多个功能入口可以发送同一命令,避免多处多次实现相同功能的冗余代码。另外可以对命令进行延迟处理,或放入队列或栈中,支持命令回撤。

二、问题

  在使用GUI界面的应用程序时,一般保存功能会有多个入口,比如点击按钮保存、点击菜单项保存和使用键盘ctrl+s保存等,每个入口的位置不同且没有什么关联关系,只是最终实现文档保存功能的代码是一样的。
在这里插入图片描述
  此时要么将这些操作代码复制粘贴进多个类中,要么就是让菜单项依赖于按钮,显然这两种方式都是不明智的。

三、解决方案

  一般在这种一对多、多对多的场景下,最好的方式是添加一个中间层,上层负责GUI的交互,下层负责业务逻辑的处理,中间层则将命令请求抽象为一个对象,在上下两层中传递消息数据,该对象可以连接不同的GUI和业务逻辑对象GUI 对象无需了解业务逻辑对象是否获得了请求,也无需了解其对请求进行处理的方式。
在这里插入图片描述
  1. 该模式的类图如下所示:发送者ICommandSender中包含了命令接口ICommand的指针,ICommand中只有execute虚函数,具体的命令类SaveCommand中包含有命令接收者Document的指针,且重写execute函数。CommandHistory类记录了每条执行了的命令,调用pop弹出命令时可以调用该命令的redo函数(下图中未展示)进行命令撤销。
在这里插入图片描述

  2. 该模式下各类之间交互的时序图如下:首先生成命令接收者Document,然后生成命令SaveCommand并绑定Document,接下来生成命令发送者Button并绑定SaveCommand,最后Button调用触发函数click让命令对象去执行具体的execute函数。
在这里插入图片描述
  3. 相关代码实现

  • 命令发送者
class CommandHistory{
public:
    static CommandHistory* GetInstance(){
        static CommandHistory cmdHis;
        return &cmdHis;
    }
    void push(ICommand* cmd){m_cmdHisVec.push(cmd);}
    void pop(){m_cmdHisVec.pop();}
    size_t size(){return m_cmdHisVec.size();}
private:
    stack<ICommand*> m_cmdHisVec;
};

#define CMD_HIS CommandHistory::GetInstance()

class ICommandSender{
public:
    ICommandSender(){}
    ~ICommandSender(){}
    void setCommand(ICommand* cmd){m_cmd = cmd;}
    ICommand* getCommand(){return m_cmd;}
protected:
    ICommand* m_cmd;
};

class Button : public ICommandSender{
public:
    void click(){
        m_cmd->execute("Sent by Button");
        CMD_HIS->push(m_cmd);
        cout << "Count of Button history command is " << CMD_HIS->size() << endl;
    }
};

class Shortcut : public ICommandSender{
public:
    void setCommand(const string& key, ICommand* cmd){
        m_cmd = cmd;
        m_keyCmdMap[key] = cmd;
    }
    void press(){
        m_cmd->execute("Sent by Shortcut");
        CMD_HIS->push(m_cmd);
        cout << "Count of Shortcut history command is " << CMD_HIS->size() << endl;
    }
private:
    map<string, ICommand*> m_keyCmdMap;
};
  • 命令
class ICommand{
public:
    virtual ~ICommand(){}
    virtual void execute(const string& from) = 0;
};
class SaveCommand : public ICommand{
public:
    SaveCommand(Document* doc) : m_doc(doc) {}
    virtual void execute(const string& from) override{
        m_doc->setText(from);
        m_doc->save();
    }
private:
    Document* m_doc;
};
  • 命令接收者
class Document{
public:
    void setText(const string& text){
        m_text = text;
    }
    void save(){
        cout << m_text << " has been saved" << endl;               
    }
private:
    string m_text;
};

四、试用场景总结

  • 多个不同操作对应同一处理结果,那么可以使用命令模式。如点击菜单项、点击按钮和ctrl+s进行保存
  • 同一操作在不同的场景下产生不同的结果,在运行时切换已连接的命令,也可以使用命令模式。如用户可以配置菜单项,在点击时触发不同的命令。
  • 若需要将操作放入队列中,延迟或计划发送命令,也可以使用命令模式。
  • 若要实现操作回滚(撤销)功能,也可以使用命令模式。命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。
  • 若要远程执行命令,需要将命令对象序列化,从而能方便地写入文件或数据库中。

五、后记

以上所有内容均为原创,代码已上传至gayhub:
https://github.com/gangster-puppy/Design-Pattern.git

更多推荐

Java基础之lambda表达式(JDK1.8新特性)

文章目录Lambda表达式各种函数式接口Lambda的语法Lambda表达实例举例说明变量作用域处理lambda表达式变量作用域函数式接口使用实例1使用实例2使用示例3(集合排序)使用示例4(按照对象属性给list排序)使用示例4总结参考Lambda表达式Lambda表达式允许把函数作为一个方法的参数(函数作为参数传递

第十四届蓝桥杯省赛 Python B 组 D 题——管道(AC)

目录1.管道1.问题描述2.输入格式3.输出格式4.样例输入5.样例输出6.评测用例规模与约定2.解题思路3.AC_Code1.管道1.问题描述有一根长度为len\text{len}len的横向的管道,该管道按照单位长度分为len\text{len}len段,每一段的中央有一个可开关的阀门和一个检测水流的传感器。一开始

OpenHarmony应用开发—ArkUI组件集合

介绍本示例为ArkUI中组件、通用、动画、全局方法的集合。效果预览使用说明:1.点击组件、通用、动画、全局方法四个按钮或左右滑动切换不同视图。2.点击二级导航(如通用属性、通用事件等),若存在三级导航则展开三级导航(如Border边框、点击事件等);若不存在三级导航,则跳转至详情页面。若存在三级导航(如Border边框

从腾讯跳槽到英特尔,真实感受爆料

腾讯已经不像互联网公司了,之前国内的互联网还是要感谢一下外企,但是后面就开始卷起来了!在卷的环境里,食堂再好吃也没有用,因为是不会补回来的,而且英特尔已经是外企的天花板了知足吧!如果办公区很有生活的气息,那只能说明这些人根本没有生活,只为事业奋斗!还有很多网友询问英特尔的工作情况和面试情况“面试和工作都是英文吗?”“英

python爬虫:新兴动态渲染工具Playwright的简单介绍和教程

Playwright是一个用于自动化浏览器操作的工具,它支持Chromium、Firefox和WebKit浏览器,并提供了Python、JavaScript和其他编程语言的API。以下是Playwright的介绍和入门教程:1.安装Playwright首先,你需要安装Playwright。在Python中,你可以使用p

PCB布线之电源线干扰?|深圳比创达EMC

一客户画户外摄像头的板子,板子上电源线非常多,6层板,电源层已经被分割完了,还有2根电源线,没办法只能并行走线了,板子画完后发主管评审,主管让其在2根电源线中间走一根地线,该客户感觉没必要,因为地线太长了,太细了会形成天线,而且中间也没有地方可以打地孔。如下图:其实直流电源之间的影响有几个基本的优先原则:电源交流分量高

蓝牙技术|蓝牙轻松部署物联网项目,智能照明利用蓝牙特性快速发展

蓝牙物联网,特别是在低功耗蓝牙(BLE)普及的推动下,在物联网领域取得了显著增长和采用。由于低能耗和长时间使用小型电池的能力,BLE已成为许多物联网应用的首选无线技术。BLE使用与蓝牙相同的无线电频段,同样可以实现两个设备之间的数据交换。主要区别在于,BLE设备在连接之间的大部分时间都处于睡眠模式,并进行简短的通信会话

基于微信小程序的校园生活管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录前言运行环境学生微信端的主要功能有:管理员的主要功能有:具体实现截图视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利代码参考源码获取前言💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ

机器学习:10种方法解决模型过拟合

机器学习:10种方法解决模型过拟合本文介绍机器学习/深度学习建模过程防止模型过拟合的10种有效方法:增加训练数据集交叉验证正则化合适的特征选择降低模型复杂度集成方法早停法EarlyStopping数据增强Dropout监控训练过程方法1:增加训练数据集增加更多的训练数据有助于防止过拟合,主要是因为更多的数据能够提供更全

【kafka实战】03 SpringBoot使用kafka生产者和消费者示例

本节主要介绍用SpringBoot进行开发时,使用kafka进行生产和消费一、引入依赖<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></de

【Java 基础篇】Java网络编程实战:P2P文件共享详解

Java网络编程是现代软件开发中不可或缺的一部分,因为它允许不同计算机之间的数据传输和通信。在本篇博客中,我们将深入探讨Java中的P2P文件共享,包括什么是P2P文件共享、如何实现它以及一些相关的重要概念。什么是P2P文件共享?P2P(Peer-to-Peer)文件共享是一种分布式计算模型,其中每个计算机或设备都可以

热文推荐