重温设计模式--命令模式
文章目录
- 命令模式的详细介绍
- C++ 代码示例
- C++代码示例2
命令模式的详细介绍
-
定义与概念
- 命令模式属于行为型设计模式,它旨在将一个请求封装成一个对象,从而让你可以用不同的请求对客户端进行参数化,将请求的发送者和接收者解耦,并且能够方便地对请求进行排队、记录请求日志,以及支持可撤销的操作等。
- 例如,在一个智能家居系统中,有各种电器设备(如灯、电视、空调等),而用户可以通过遥控器(类似调用者)发出各种操作指令(如开灯、关电视、调空调温度等,这些指令就是不同的命令),每个电器设备就是接收者,它们知道如何具体执行对应的操作。通过命令模式,可以把这些操作指令都封装成一个个独立的命令对象,这样遥控器就可以方便地调用不同的命令来控制不同的电器,而且便于系统进行扩展、管理和实现诸如撤销操作等功能。
-
角色构成及职责
- 命令(Command)接口或抽象类:这是整个模式的核心抽象,它声明了执行操作的方法,通常是一个名为
execute的纯虚函数(在 C++ 中)。其作用是为所有具体命令类提供统一的执行接口规范,使得调用者可以用统一的方式来调用不同的具体命令。 - 具体命令(ConcreteCommand)类:实现了
Command接口,内部持有一个接收者(Receiver)对象的引用。在execute方法中,它会调用接收者对象相应的方法来完成具体的操作。例如,对于“开灯”这个具体命令,它的execute方法里就会调用灯(接收者)对象的“点亮”方法来实际执行开灯操作。 - 接收者(Receiver)类:接收者是真正知道如何执行具体业务逻辑和操作的对象,它包含了与实际操作相关的方法。不同的接收者对应不同的功能实体,比如电器设备等,每个接收者的方法实现了具体要做的事情,像灯的亮灭、电视的开关频道切换等操作都是在接收者类里定义方法实现的。
- 调用者(Invoker)类:负责触发命令的执行,它持有一个或多个命令对象的引用,可以通过调用命令对象的
execute方法来让命令生效。调用者可以管理命令的执行顺序,例如可以将多个命令按顺序放入一个队列中然后依次执行;也能方便地实现一些高级功能,比如存储历史命令以便支持撤销和重做操作等。

- 命令(Command)接口或抽象类:这是整个模式的核心抽象,它声明了执行操作的方法,通常是一个名为
-
优点
- 解耦请求发送者和接收者:发送者不需要知道接收者具体的实现细节以及如何执行操作,只需要调用命令对象的执行方法就行,这样双方的依赖关系变得松散,便于各自独立修改和扩展。
- 方便实现撤销和重做功能:通过记录已经执行过的命令对象,可以很容易地实现撤销操作(按照一定规则反向执行之前的命令)以及重做操作(再次执行已经撤销的命令),这在很多应用场景中非常有用,比如文本编辑器的撤销和重做功能。
- 增强代码的可扩展性和可维护性:新增加具体命令或者接收者都相对容易,只需要实现对应的接口或者继承相应的抽象类,然后按照规则整合到系统中即可,不会对现有代码结构造成大规模的破坏。
-
缺点
- 增加了代码的复杂性:引入了多个类和接口来实现命令模式,相比于直接调用方法实现功能,整体代码结构变得更复杂,对于简单的场景来说可能有点“大材小用”,会让代码理解和维护成本在一定程度上提高。
- 可能存在过多的小类:每一个具体的命令都需要对应一个具体命令类,如果有大量不同的命令,会导致类的数量增多,不过这可以通过合理的设计和适当的抽象来缓解。
-
应用场景
- 图形界面操作:例如在绘图软件中,像绘制图形、移动图形、删除图形等操作都可以封装成不同的命令,方便用户通过菜单、快捷键等方式触发,也便于实现撤销和重做功能。
- 游戏开发:游戏中角色的各种动作(如攻击、跳跃、移动等)可以看作是不同的命令,由玩家输入(调用者)触发,然后游戏角色(接收者)执行相应的动作,并且可以记录操作历史来实现一些回滚操作等功能。
- 任务队列系统:把不同的任务封装成命令放入队列中,按照顺序依次执行,便于对任务进行统一管理和调度,比如后台服务器处理各种业务请求任务等场景。
C++ 代码示例
以下是一个简单的模拟遥控器控制电器设备的 C++ 代码示例,体现了命令模式的基本结构和用法:
#include <iostream>
#include <vector>
#include <memory>// 命令接口
class Command
{
public:virtual void execute() = 0;
};// 接收者 - 灯类,代表一个可以被控制的电器设备
class Light
{
public:void turnOn(){std::cout << "Light is turned on." << std::endl;}void turnOff(){std::cout << "Light is turned off." << std::endl;}
};// 具体命令 - 开灯命令
class LightOnCommand : public Command
{
private:std::shared_ptr<Light> light;
public:LightOnCommand(std::shared_ptr<Light> l) : light(l) {}void execute() override{light->turnOn();}
};// 具体命令 - 关灯命令
class LightOffCommand : public Command
{
private:std::shared_ptr<Light> light;
public:LightOffCommand(std::shared_ptr<Light> l) : light(l) {}void execute() override{light->turnOff();}
};// 调用者 - 遥控器类
class RemoteControl
{
private:std::vector<std::shared_ptr<Command>> onCommands;std::vector<std::shared_ptr<Command>> offCommands;
public:RemoteControl(){onCommands.resize(7);offCommands.resize(7);}void setCommand(int slot, std::shared_ptr<Command> onCommand, std::shared_ptr<Command> offCommand){onCommands[slot] = onCommand;offCommands[slot] = offCommand;}void onButtonWasPushed(int slot) {if (onCommands[slot]) {onCommands[slot]->execute();}}void offButtonWasPushed(int slot) {if (offCommands[slot]){offCommands[slot]->execute();}}
};int main()
{// 创建灯对象std::shared_ptr<Light> livingRoomLight = std::make_shared<Light>();// 创建具体命令对象std::shared_ptr<LightOnCommand> livingRoomLightOn = std::make_shared<LightOnCommand>(livingRoomLight);std::shared_ptr<LightOffCommand> livingRoomLightOff = std::make_shared<LightOffCommand>(livingRoomLight);// 创建遥控器对象RemoteControl remote;remote.setCommand(0, livingRoomLightOn, livingRoomLightOff);// 按下遥控器开灯按钮remote.onButtonWasPushed(0);// 按下遥控器关灯按钮remote.offButtonWasPushed(0);return 0;
}
在上述代码中:
Command接口定义了统一的执行操作的抽象方法execute。Light类作为接收者,包含了灯的实际操作方法turnOn和turnOff。LightOnCommand和LightOffCommand是具体命令类,它们分别关联了灯对象,并在execute方法中调用对应的灯操作方法来实现开灯和关灯的命令功能。RemoteControl类作为调用者,通过setCommand方法可以设置不同按钮对应的开和关命令,然后通过onButtonWasPushed和offButtonWasPushed方法来触发相应命令的执行,模拟了遥控器控制灯的操作过程。
这个示例只是一个基础的展示,你可以根据实际需求进一步扩展,比如添加更多的电器设备和对应的命令,或者实现命令的撤销、重做等功能(就像前面介绍中提到的那样,可以通过记录已执行的命令列表等方式来实现)。
C++代码示例2
#include<iostream>
#include<list>
using namespace std;
//将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化//厨师类
class C_COOK
{
public:virtual void docooking(){cout<<"111111111"<<endl;}
};//广东厨师
class GuangDongCook: public C_COOK
{
public:virtual void docooking(){cout<<"广东菜,淡、淡、淡"<<endl;}
};//四川厨师
class SiChuanCook: public C_COOK
{
public:virtual void docooking(){cout<<"四川菜,辣、辣、辣"<<endl;}
};//菜点
class Food
{
public:virtual void cook(){}
};//广东菜
class Guangdongfood : public Food
{
private:C_COOK *m_cook;
public:Guangdongfood(C_COOK *p_cook):m_cook(p_cook){}void cook(){m_cook->docooking();}
};//四川菜
class SiChuanfood : public Food
{
private:C_COOK *m_cook;
public:SiChuanfood(C_COOK *p_cook):m_cook(p_cook){}void cook(){m_cook->docooking();}
};//服务员
class Waiter
{list<Food*>ls;
public:void SetOrder(Food *p_food){ls.push_back(p_food);}void POST(){list<Food*>::iterator itr = ls.begin();for(;itr!=ls.end();++itr){std::cout<<typeid(*itr).name()<<endl;//打印出来类型,在这里还是Food *类型(*itr)->cook();//对应的师傅开始做菜//在此处调用开始出现多态,//第一次push进来的是 Food *sifood = new SiChuanfood(m_suicook);//实际类型是 SiChuanfood * 当调用时进行RTTI运行时类型识别 识别为SiChuanfood*//进而调用 cout<<"四川菜,辣、辣、辣"<<endl;}}
};int main()
{C_COOK *m_suicook = new SiChuanCook();C_COOK*m_gdcook = new GuangDongCook();Food *sifood = new SiChuanfood(m_suicook);Food*gdfood = new Guangdongfood(m_gdcook);Waiter xiaoli;xiaoli.SetOrder(sifood);//记录xiaoli.SetOrder(gdfood);//记录xiaoli.POST();//通知return 0;
}输出如下
class Food *
四川菜,辣、辣、辣
class Food *
广东菜,淡、淡、淡
请按任意键继续. . .
如果要是再增加一个湖南菜,这时需要加一个湖南菜的类和湖南厨师类,代码如下
#include<iostream>
#include<list>
using namespace std;
//将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化//厨师类
class C_COOK
{
public:virtual void docooking(){cout<<"111111111"<<endl;}
};//广东厨师
class GuangDongCook: public C_COOK
{
public:virtual void docooking(){cout<<"广东菜,淡、淡、淡"<<endl;}
};//四川厨师
class SiChuanCook: public C_COOK
{
public:virtual void docooking(){cout<<"四川菜,辣、辣、辣"<<endl;}
};//湖南厨师
class HUnanCook: public C_COOK
{
public:virtual void docooking(){cout<<"湖南菜,贼辣、贼辣、贼辣"<<endl;}
};//菜点
class Food
{
public:virtual void cook(){}
};//广东菜
class Guangdongfood : public Food
{
private:C_COOK *m_cook;
public:Guangdongfood(C_COOK *p_cook):m_cook(p_cook){}void cook(){m_cook->docooking();}
};//四川菜
class SiChuanfood : public Food
{
private:C_COOK *m_cook;
public:SiChuanfood(C_COOK *p_cook):m_cook(p_cook){}void cook(){m_cook->docooking();}
};//新增//湖南菜
class Hunanfood : public Food
{
private:C_COOK *m_cook;
public:Hunanfood(C_COOK *p_cook):m_cook(p_cook){}void cook(){m_cook->docooking();}
};//服务员
class Waiter
{list<Food*>ls;
public:void SetOrder(Food *p_food){ls.push_back(p_food);}void POST(){list<Food*>::iterator itr = ls.begin();for(;itr!=ls.end();++itr){std::cout<<typeid(*itr).name()<<endl;//打印出来类型,在这里还是Food *类型(*itr)->cook();//在此处调用开始出现多态,//第一次push进来的是 Food *sifood = new SiChuanfood(m_suicook);//实际类型是 SiChuanfood * 当调用时进行RTTI运行时类型识别 识别为SiChuanfood*//进而调用 cout<<"四川菜,辣、辣、辣"<<endl;}}
};int main()
{C_COOK *m_suicook = new SiChuanCook();C_COOK*m_gdcook = new GuangDongCook();C_COOK*m_hncook = new HUnanCook();Food *sifood = new SiChuanfood(m_suicook);Food*gdfood = new Guangdongfood(m_gdcook);Food*hnfood = new Hunanfood(m_hncook);Waiter xiaoli;xiaoli.SetOrder(sifood);xiaoli.SetOrder(gdfood);xiaoli.SetOrder(hnfood);xiaoli.POST();return 0;
}结果如下
class Food *
四川菜,辣、辣、辣
class Food *
广东菜,淡、淡、淡
class Food *
湖南菜,贼辣、贼辣、贼辣
请按任意键继续. . .相关文章:
重温设计模式--命令模式
文章目录 命令模式的详细介绍C 代码示例C代码示例2 命令模式的详细介绍 定义与概念 命令模式属于行为型设计模式,它旨在将一个请求封装成一个对象,从而让你可以用不同的请求对客户端进行参数化,将请求的发送者和接收者解耦,并且能…...
电力通信规约-104实战
电力通信规约-104实战 概述 104规约在广泛应用于电力系统远动过程中,主要用来进行数据传输和转发,本文将结合实际开发实例来讲解104规约的真实使用情况。 实例讲解 因为个人技术栈是Java,所以本篇将采用Java实例来进行讲解。首先我们搭建一…...
什么是事务
在数据库管理系统中,事务(Transaction)是执行一系列操作的最小工作单元,这些操作要么全部成功,要么全部失败。为了确保数据的一致性和完整性,事务被设计为具备四大特性,即原子性(Ato…...
数据结构:双向循坏链表
目录 1.1双向循环链表的结构 2.双向链表功能的实现 2.1初始化链表 2.2销毁链表 2.3创建结点 2.4打印链表 2.5链表查找 2.6链表在pos的前面进行插入 2.7链表删除pos位置的节点 2.8链表的头插,头删 ,尾插,尾删 1.1双向循环链表的结构 …...
3.1、SDH的5种标准容器
1、定义与作用 在 SDH(同步数字体系)中,标准容器(C)是一种用来装载各种速率的 PDH(准同步数字系列)信号的信息结构。它的主要作用是进行速率适配,使不同速率的 PDH 信号能够在 SDH 的…...
Jenkins介绍
Jenkins 是一款流行的开源自动化服务器,在软件开发和持续集成 / 持续交付(CI/CD)流程中发挥着关键作用。 一、主要功能 1.持续集成(CI) (1).自动构建:Jenkins 可以配置为监听代码仓…...
5G学习笔记之Non-Public Network
目录 0. NPN系列 1. 概述 2. SNPN 2.1 SNPN概述 2.2 SNPN架构 2.3 SNPN部署 2.3.1 完全独立 2.3.2 共享PLMN基站 2.3.3 共享PLMN基站和PLMN频谱 3. PNI-NPN 3.1 PNI-NPN概述 3.2 PNI-NPN部署 3.2.1 UPF独立 3.2.2 完全共享 0. NPN系列 1. NPN概述 2. NPN R18 3. 【SNPN系列】S…...
网页生成鸿蒙App
如何网页生成鸿蒙App 纯鸿蒙发布后,鸿蒙App需求上升。如何快速生成鸿蒙App。变色龙云(http://www.appbsl.cn)推出了鸿蒙App打包服务。可以在线自动打包鸿蒙App。 第一步 创建应用 输入网站网址,上传图标。 第二步 生成鸿蒙证书 打开华为开发者管理中…...
JavaWeb通过Web查询数据库内容:(pfour_webquerymysql)
JavaWeb通过Web查询数据库内容: 数据库: 自行建库建表,主键 id 后端: 新建项目模块选择模块,添加依赖创建配置文件: db.propertiesJava类: query查询 前端: Web添加创建query.html…...
将java项目部署到linux
命令解析 Dockerfile: Dockerfile 是一个文本文件,包含了所有必要的指令来组装(build)一个 Docker 镜像。 docker build: 根据 Dockerfile 或标准指令来构建一个新的镜像。 docker save: 将本地镜像保存为一个 tar 文件。 docker load: 从…...
moviepy将图片序列制作成视频并加载字幕 - python 实现
DataBall 助力快速掌握数据集的信息和使用方式,会员享有 百种数据集,持续增加中。 需要更多数据资源和技术解决方案,知识星球: “DataBall - X 数据球(free)” -------------------------------------------------------------…...
ROS1入门教程5:简单行为处理
一、新建项目 # 创建工作空间 mkdir -p demo5/src && cd demo5# 初始化工作空间 catkin_make# 创建功能包 cd src catkin_create_pkg demo roscpp actionlib_msgs message_generation tf 二、创建行为 # 创建行为目录 mkdir action && cd action# 创建行为文…...
Vue:实现输入框不能输负数功能
1、使用v-model指令 <input type"number" v-model"value" min"0" input"checkInput"> checkInput() {this.value Math.max(0, parseInt(this.value)); } 2、使用计算属性 <template><div><input type"…...
管理系统、微信小程序类源码文档-哔哩哔哩教程同步
文章目录 前言通用表基于JavaSpringBootVue前后端分离手机销售商城系统设计实现:基于JavaSpringBootVueuniapp实现大学生校园兼职微信小程序更新中。。。评论区打出你的题目 🌈你好呀!我是 山顶风景独好 🎈欢迎踏入我的博客世界,能…...
AOP切点表达式之方法表达式execution
天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…...
clickhouse-题库
1、clickhouse介绍以及架构 clickhouse一个分布式列式存储数据库,主要用于在线分析查询 2、列式存储和行式存储有什么区别? 行式存储: 1)、数据是按行存储的 2)、没有建立索引的查询消耗很大的IO 3)、建…...
在 Sanic 应用中使用内存缓存管理 IP 黑名单
[外链图片转存中…(img-Pm0K9mzd-1734859380698)] 在现代 web 应用中,保护 API 接口免受恶意请求的攻击至关重要。IP 黑名单是一种常见的安全措施,可以有效阻止某些 IP 地址的访问。本文将介绍如何在 Python 的 Sanic 框架中实现 IP 黑名单功能…...
可翻折的CPCI导冷板卡插拔机构
本技术涉及一种cpci板卡模块拔插,尤其涉及一种可翻折的cpci导冷板卡插拔机构。 背景技术: 1、cpci(compactpci)导冷板卡是一种基于compactpci计算机总线标准的卡式电子模块,它在标准的cpci架构之上增加了导热板来提高散热能力,常…...
面试题整理9----谈谈对k8s的理解2
面试题整理9----谈谈对k8s的理解2 1. Service 资源1.1 ServiceClusterIPNodePortLoadBalancerIngressExternalName 1.2 Endpoints1.3 Ingress1.4 EndpointSlice1.5 IngressClass 2. 配置和存储资源2.1 ConfigMap2.2 Secret2.3 PersistentVolume2.4 PersistentVolumeClaim2.5 St…...
12个城市人文扫街、旅拍、人像风光摄影后期Lightroom调色预设
12个城市人文扫街、旅拍、人像风光摄影后期Lightroom调色预设 12 个专为城市场景设计的专业 Adobe Lightroom 预设。只需单击一下,即可通过一致、专业的基础简化您的编辑流程。 Lightroom & Lightroom Classic,桌面和移动兼容包括 12 张由 pat_kay…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
