重温设计模式--命令模式

文章目录

      • 命令模式的详细介绍
      • C++ 代码示例
      • C++代码示例2

命令模式的详细介绍

  1. 定义与概念

    • 命令模式属于行为型设计模式,它旨在将一个请求封装成一个对象,从而让你可以用不同的请求对客户端进行参数化,将请求的发送者和接收者解耦,并且能够方便地对请求进行排队、记录请求日志,以及支持可撤销的操作等。
    • 例如,在一个智能家居系统中,有各种电器设备(如灯、电视、空调等),而用户可以通过遥控器(类似调用者)发出各种操作指令(如开灯、关电视、调空调温度等,这些指令就是不同的命令),每个电器设备就是接收者,它们知道如何具体执行对应的操作。通过命令模式,可以把这些操作指令都封装成一个个独立的命令对象,这样遥控器就可以方便地调用不同的命令来控制不同的电器,而且便于系统进行扩展、管理和实现诸如撤销操作等功能。
  2. 角色构成及职责

    • 命令(Command)接口或抽象类:这是整个模式的核心抽象,它声明了执行操作的方法,通常是一个名为 execute 的纯虚函数(在 C++ 中)。其作用是为所有具体命令类提供统一的执行接口规范,使得调用者可以用统一的方式来调用不同的具体命令。
    • 具体命令(ConcreteCommand)类:实现了 Command 接口,内部持有一个接收者(Receiver)对象的引用。在 execute 方法中,它会调用接收者对象相应的方法来完成具体的操作。例如,对于“开灯”这个具体命令,它的 execute 方法里就会调用灯(接收者)对象的“点亮”方法来实际执行开灯操作。
    • 接收者(Receiver)类:接收者是真正知道如何执行具体业务逻辑和操作的对象,它包含了与实际操作相关的方法。不同的接收者对应不同的功能实体,比如电器设备等,每个接收者的方法实现了具体要做的事情,像灯的亮灭、电视的开关频道切换等操作都是在接收者类里定义方法实现的。
    • 调用者(Invoker)类:负责触发命令的执行,它持有一个或多个命令对象的引用,可以通过调用命令对象的 execute 方法来让命令生效。调用者可以管理命令的执行顺序,例如可以将多个命令按顺序放入一个队列中然后依次执行;也能方便地实现一些高级功能,比如存储历史命令以便支持撤销和重做操作等。
      在这里插入图片描述
  3. 优点

    • 解耦请求发送者和接收者:发送者不需要知道接收者具体的实现细节以及如何执行操作,只需要调用命令对象的执行方法就行,这样双方的依赖关系变得松散,便于各自独立修改和扩展。
    • 方便实现撤销和重做功能:通过记录已经执行过的命令对象,可以很容易地实现撤销操作(按照一定规则反向执行之前的命令)以及重做操作(再次执行已经撤销的命令),这在很多应用场景中非常有用,比如文本编辑器的撤销和重做功能。
    • 增强代码的可扩展性和可维护性:新增加具体命令或者接收者都相对容易,只需要实现对应的接口或者继承相应的抽象类,然后按照规则整合到系统中即可,不会对现有代码结构造成大规模的破坏。
  4. 缺点

    • 增加了代码的复杂性:引入了多个类和接口来实现命令模式,相比于直接调用方法实现功能,整体代码结构变得更复杂,对于简单的场景来说可能有点“大材小用”,会让代码理解和维护成本在一定程度上提高。
    • 可能存在过多的小类:每一个具体的命令都需要对应一个具体命令类,如果有大量不同的命令,会导致类的数量增多,不过这可以通过合理的设计和适当的抽象来缓解。
  5. 应用场景

    • 图形界面操作:例如在绘图软件中,像绘制图形、移动图形、删除图形等操作都可以封装成不同的命令,方便用户通过菜单、快捷键等方式触发,也便于实现撤销和重做功能。
    • 游戏开发:游戏中角色的各种动作(如攻击、跳跃、移动等)可以看作是不同的命令,由玩家输入(调用者)触发,然后游戏角色(接收者)执行相应的动作,并且可以记录操作历史来实现一些回滚操作等功能。
    • 任务队列系统:把不同的任务封装成命令放入队列中,按照顺序依次执行,便于对任务进行统一管理和调度,比如后台服务器处理各种业务请求任务等场景。

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 类作为接收者,包含了灯的实际操作方法 turnOnturnOff
  • LightOnCommandLightOffCommand 是具体命令类,它们分别关联了灯对象,并在 execute 方法中调用对应的灯操作方法来实现开灯和关灯的命令功能。
  • RemoteControl 类作为调用者,通过 setCommand 方法可以设置不同按钮对应的开和关命令,然后通过 onButtonWasPushedoffButtonWasPushed 方法来触发相应命令的执行,模拟了遥控器控制灯的操作过程。

这个示例只是一个基础的展示,你可以根据实际需求进一步扩展,比如添加更多的电器设备和对应的命令,或者实现命令的撤销、重做等功能(就像前面介绍中提到的那样,可以通过记录已执行的命令列表等方式来实现)。

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 *
湖南菜,贼辣、贼辣、贼辣
请按任意键继续. . .

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/941633.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python langchain ReAct 使用范例

0. 介绍 ReAct: Reasoning Acting &#xff0c;ReAct Prompt 由 few-shot task-solving trajectories 组成&#xff0c;包括人工编写的文本推理过程和动作&#xff0c;以及对动作的环境观察。 1. 范例 langchain version 0.3.7 $ pip show langchain Name: langchain Ver…

Java设计模式 —— 【结构型模式】外观模式详解

文章目录 概述结构案例实现优缺点 概述 外观模式又名门面模式&#xff0c;是一种通过为多个复杂的子系统提供一个一致的接口&#xff0c;而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口&#xff0c;外部应用程序不用关心内部子系统的具体的细节&#xff0c;这…

【自用】通信内网部署rzgxxt项目_01,后端pipeDemo部署(使用nssm.exe仿照nohup)

做完这些工作之后&#xff0c;不要忘记打开 Windows Server 的防火墙端口&#xff0c;8181、8081、8080、22、443、1521 做完这些工作之后&#xff0c;不要忘记打开 Windows Server 的防火墙端口&#xff0c;8181、8081、8080、22、443、1521 做完这些工作之后&#xff0c;不要…

Apache RocketMQ 5.1.3安装部署文档

官方文档不好使&#xff0c;可以说是一坨… 关键词&#xff1a;Apache RocketMQ 5.0 JDK 17 废话少说&#xff0c;开整。 1.版本 官网地址&#xff0c;版本如下。 https://rocketmq.apache.org/download2.配置文件 2.1namesrv端口 在ROCKETMQ_HOME/conf下 新增namesrv.pro…

【网络安全】网站常见安全漏洞—服务端漏洞介绍

文章目录 网站常见安全漏洞—服务端漏洞介绍引言1. 第三方组件漏洞什么是第三方组件漏洞&#xff1f;如何防范&#xff1f; 2. SQL 注入什么是SQL注入&#xff1f;如何防范&#xff1f; 3. 命令执行漏洞什么是命令执行漏洞&#xff1f;如何防范&#xff1f; 4. 越权漏洞什么是越…

【计算机视觉基础CV-图像分类】01- 从历史源头到深度时代:一文读懂计算机视觉的进化脉络、核心任务与产业蓝图

1.计算机视觉定义 计算机视觉&#xff08;Computer Vision&#xff09;是一个多学科交叉的研究领域&#xff0c;它的核心目标是使计算机能够像人类一样“看”并“理解”视觉信息。换句话说&#xff0c;它希望赋予计算机从图像、视频中自动提取、有意义地分析、理解并解释视觉场…

JVM系列(十三) -常用调优工具介绍

最近对 JVM 技术知识进行了重新整理&#xff0c;再次献上 JVM系列文章合集索引&#xff0c;感兴趣的小伙伴可以直接点击如下地址快速阅读。 JVM系列(一) -什么是虚拟机JVM系列(二) -类的加载过程JVM系列(三) -内存布局详解JVM系列(四) -对象的创建过程JVM系列(五) -对象的内存分…

electron-vite【实战】登录/注册页

效果预览 项目搭建 https://blog.csdn.net/weixin_41192489/article/details/144611858 技术要点 路由默认跳转到登录页 src/renderer/src/router/index.ts routes: [// 默认跳转到登录页{path: /,redirect: /login},...routes]登录窗口的必要配置 src/main/index.ts 中 cons…

蓝桥杯嵌入式备赛教程(1、led,2、lcd,3、key)

一、工程模版创建流程 第一步 创建新项目 第二步 选择型号和管脚封装 第三步 RCC使能 外部时钟&#xff0c;高速外部时钟 第四步晶振时钟配置 由数据手册7.1可知外部晶振频率为24MHz 最后一项设置为80 按下回车他会自动配置时钟 第五步&#xff0c;如果不勾选可能程序只会…

C++----------函数的调用机制

栈帧的创建与销毁 栈帧创建过程 当一个函数被调用时&#xff0c;系统会在程序的栈空间中为该函数创建一个栈帧。首先&#xff0c;会将函数的返回地址&#xff08;即调用该函数的下一条指令的地址&#xff09;压入栈中&#xff0c;这确保函数执行完后能回到正确的位置继续执行后…

C语言初阶习题【9】数9的个数

1.编写程序数一下 1到 100 的所有整数中出现多少个数字9 2.思路 循环遍历1到100&#xff0c;需要判断每一位的个位数是否为9&#xff0c;十位数是否为9&#xff0c;每次符合条件就count进行计数&#xff0c;最后输出count&#xff0c;即可 3.code #define _CRT_SECURE_NO_W…

模型 课题分离

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。明确自我与他人责任。 1 课题分离的应用 1.1课题分离在心理治疗中的应用案例&#xff1a;李晓的故事 李晓&#xff0c;一位28岁的软件工程师&#xff0c;在北京打拼。他面临着工作、家庭和感情的多重…

Docker 入门:如何使用 Docker 容器化 AI 项目(一)

引言 在人工智能&#xff08;AI&#xff09;项目的开发和部署过程中&#xff0c;环境配置和依赖管理往往是开发者遇到的挑战之一。开发者通常需要在不同的机器上运行同样的代码&#xff0c;确保每个人使用的环境一致&#xff0c;才能避免 “在我的机器上可以运行”的尴尬问题。…

Android修行手册 - 移动端几种常用动画方案对比

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

抢单人机交互「新红利」!哪些细分赛道“多金”?

受终端用户的智能座舱体验需求驱动&#xff0c;视觉、听觉、触觉等人机交互方式加速焕新。 一方面&#xff0c;人机多模交互引领&#xff0c;车载声学进入新周期。根据高工智能汽车研究院统计数据&#xff0c;单车的车载扬声器搭载量正在快速起量。 很显然&#xff0c;作为智…

Linux shell脚本用于常见图片png、jpg、jpeg、webp、tiff格式批量添加文本水印

Linux Debian12基于ImageMagick图像处理工具编写shell脚本用于常见图片png、jpg、jpeg、webp、tiff格式批量添加文本水印 BiliBili视频链接&#xff1a; Linux shell脚本对常见图片格式转换webp和添加文本水印 在Linux系统中&#xff0c;使用ImageMagick可以图片格式转换&…

本地电脑使用命令行上传文件至远程服务器

将本地文件上传到远程服务器&#xff0c;在本地电脑中cmd使用该命令&#xff1a; scp C:/Users/"你的用户名"/Desktop/environment.yml ws:~/environment.yml 其中&#xff0c;C:/Users/“你的用户名”/Desktop/environment.yml是本地文件的路径&#xff0c; ~/en…

004最长回文子串

#include #include #include using namespace std; class Solution { public: string longestPalindrome(string s) { int n s.size(); if (n < 2) { return s; } int maxLen 1;int begin 0;// dp[i][j] 表示 s[i..j] 是否是回文串vector<vector<int>> …

蓝桥杯刷题——day8

蓝桥杯刷题——day8 题目一题干解题思路代码 题目二题干解题思路代码 题目一 题干 N 架飞机准备降落到某个只有一条跑道的机场。其中第i架飞机在 Ti时刻到达机场上空&#xff0c;到达时它的剩余油料还可以继续盘旋 Di个单位时间&#xff0c;即它最早可以于 Ti时刻开始降落&am…

ue5 pcg(程序内容生成)真的简单方便,就5个节点

总结&#xff1a; 前情提示 鼠标单击右键平移节点 1.编辑-》插件-》procedural->勾选两个插件 2.右键-》pcg图表-》拖拽进入场景 3.先看点point 右键-》调试(快捷键d)->右侧设置粒子数 3.1调整粒子数 可以在右侧输入框&#xff0c;使用加减乘除 4.1 表面采样器 …