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

news2024/12/24 10:46:30

文章目录

      • 命令模式的详细介绍
      • 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 *
湖南菜,贼辣、贼辣、贼辣
请按任意键继续. . .

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于STM32U575RIT6的智能除湿器

项目说明 除湿器原理 知识点 GPIO、定时器、中断、ADC、LCD屏幕、SHT20、SPI、IIC、UART 功能概述 模块功能LCD屏幕显示温湿度&#xff0c;风机开关情况&#xff0c;制冷 开关情况&#xff0c;加热片开关情况&#xff0c;温 湿度上下阈值&#xff0c;设备ID&#xff0c;电…

【电商搜索】CRM: 具有可控条件的检索模型

【电商搜索】CRM: 具有可控条件的检索模型 目录 文章目录 【电商搜索】CRM: 具有可控条件的检索模型目录文章信息摘要研究背景问题与挑战如何解决核心创新点算法模型实验效果&#xff08;包含重要数据与结论&#xff09;相关工作后续优化方向 后记 https://arxiv.org/pdf/2412.…

【python自动化六】UI自动化基础-selenium的使用

selenium是目前用得比较多的UI自动化测试框架&#xff0c;支持java&#xff0c;python等多种语言&#xff0c;目前我们就选用selenium来做UI自动化。 1.selenium安装 安装命令 pip install selenium2.selenium的简单使用 本文以chrome浏览器为例&#xff0c;配套selenium中c…

Sigrity Optimize PI CapGen仿真教程文件路径

为了方便读者能够快速上手和学会Sigrity Optimize PI和 Deacap Generate 的功能&#xff0c;将Sigrity Optimize PI CapGen仿真教程专栏所有文章对应的实例文件上传至以下路径 https://download.csdn.net/download/weixin_54787054/90171471?spm1001.2014.3001.5503

免费线上签字小程序,开启便捷电子签名

虽如今数字化飞速发展的时代&#xff0c;但线上签名小程序的开发制作却并非易事。需要攻克诸多技术难题&#xff0c;例如确保签名的真实性与唯一性&#xff0c;防止签名被伪造或篡改。 要精准地捕捉用户手写签名的笔迹特征&#xff0c;无论是笔画的粗细、轻重&#xff0c;还是…

02、服务器的分类和开发项目流程

硬件介绍 1、服务器分类2.开发流程 1、服务器分类 1.1 服务器分类 1u服务器&#xff08;u表示服务器的厚度&#xff09; 1U4.45cm&#xff1b; 4u服务器&#xff08;u表示服务器的厚度&#xff09; &#xff0c; 服务器有两个电源模块&#xff0c;接在不同的电源&#xff0c;…

canvas绘制仪表盘刻度盘

canvas画布可以实现在网页上绘制图形的方法&#xff0c;比如图表、图片处理、动画、游戏等。今天我们在vue模板下用canvas实现仪表盘的绘制。 对canvas不熟悉的同学可以先了解下canvas的API文档&#xff1a;canvas API中文网 - Canvas API中文文档首页地图 一、创建模板&#…

搭建Alist(Windows系统环境下的)并挂载阿里云盘open映射到公网

文章目录 前言1. 使用Docker本地部署Alist1.1 本地部署 Alist1.2 访问并设置Alist1.3 在管理界面添加存储 2. 安装cpolar内网穿透 前言 本文将讲解如何在 Windows 系统中借助 Docker 部署 Alist 这一强大的全平台网盘工具&#xff0c;并结合 cpolar 内网穿透&#xff0c;实现随…

【QT常用技术讲解】发送POST包(两种方式:阻塞方式及非阻塞方式)

前言 http/https(应用层)协议是广泛使用的网络通信协议。在很多与第三方API对接的场景中&#xff0c;通常是通过http/https协议完成&#xff0c;比如API对接时&#xff0c;通常要通过POST包获取access_token进行鉴权&#xff0c;然后再进行数据交互&#xff08;本篇也包含有对接…

【电商搜索】文档的信息论生成聚类

【电商搜索】文档的信息论生成聚类 目录 文章目录 【电商搜索】文档的信息论生成聚类目录文章信息概览研究背景技术挑战如何破局技术应用主要相关工作与参考文献后续优化方向 后记 文章信息 https://arxiv.org/pdf/2412.13534 概览 本文提出了一种基于信息论的生成聚类&#…

【数据结构与算法】排序算法(下)——计数排序与排序总结

写在前面 书接上文&#xff1a;【数据结构与算法】排序算法(中)——交换排序之快速排序 文章主要讲解计数排序的细节与分析源码。之后进行四大排序的总结。 文章目录 写在前面一、计数排序(非比较排序)代码的实现&#xff1a; 二、排序总结 2.1、稳定性 3.2、排序算法复杂度及…

Unity全局雾效

1、全局雾效是什么 全局雾效&#xff08;Global Fog&#xff09;是一种视觉效果&#xff0c;用于在3D场景中模拟大气中的雾气对远处物体的遮挡 它通过在场景中加入雾的效果&#xff0c;使得距离摄像机较远的物体看起来逐渐被雾气覆盖&#xff0c;从而创造出一种朦胧、模糊的视…

Kafka Streams 在监控场景的应用与实践

作者&#xff1a;来自 vivo 互联网服务器团队- Pang Haiyun 介绍 Kafka Streams 的原理架构&#xff0c;常见配置以及在监控场景的应用。 一、背景 在当今大数据时代&#xff0c;实时数据处理变得越来越重要&#xff0c;而监控数据的实时性和可靠性是监控能力建设最重要的一环…

数据分析思维(五):分析方法——假设检验分析方法

数据分析并非只是简单的数据分析工具三板斧——Excel、SQL、Python&#xff0c;更重要的是数据分析思维。没有数据分析思维和业务知识&#xff0c;就算拿到一堆数据&#xff0c;也不知道如何下手。 推荐书本《数据分析思维——分析方法和业务知识》&#xff0c;本文内容就是提取…

解读DiffusionNER: Boundary Diffusion for Named Entity Recognition

content 摘要1. 图1图21. 上方&#xff1a;扩散过程与实体边界2. 下方&#xff1a;网络结构&#xff08;Sentence Encoder Entity Decoder&#xff09;3. 关键思想小结 摘要 主要内容分为四个部分&#xff1a; 模型定位与基本原理&#xff1a; 提出了DiffusionNER模型将命名…

【QSS样式表 - ⑥】:QPushButton控件样式

文章目录 QPushBUtton控件样式QSS示例 QPushBUtton控件样式 常用子控件 常用伪状态 QSS示例 代码&#xff1a; QPushButton {background-color: #99B5D1;color: white;font-weigth: bold;border-radius: 20px; }QPushButton:hover {background-color: red; }QPushButton:p…

数字经济下的 AR 眼镜

目录 1. &#x1f4c2; AR 眼镜发展历史 1.1 AR 眼镜相关概念 1.2 市面主流 XR 眼镜 1.3 AR 眼镜大事记 1.4 国内外 XR 眼镜 1.5 国内 AR 眼镜四小龙 2. &#x1f531; 关键技术 2.1 AR 眼镜近眼显示原理 2.2 AR 眼镜关键技术 2.3 AR 眼镜技术难点 3. &#x1f4a…

smb和nfs双栈协议共享目录

1 简介 NFS和SAMBA协议都是文件共享&#xff0c;Linux客户端常用于NFS协议访问远程共享目录&#xff0c;Windows客户端常用于SAMBA协议访问远程共享目录。 2 环境 合计使用三台服务器&#xff0c;服务器都位于同一个子网&#xff08;10.0.0.0/19&#xff09;、同一个安全组…

Day13 用Excel表体验梯度下降法

Day13 用Excel表体验梯度下降法 用所学公式创建Excel表 用Excel表体验梯度下降法 详见本Day文章顶部附带资源里的Excel表《梯度下降法》&#xff0c;可以对照表里的单元格公式进行理解&#xff0c;还可以多尝试几次不同的学习率 η \eta η来感受&#xff0c;只需要更改学习率…

Python获取系统负载并打印折线图

#! /opt/py36/bin/python import psutil import matplotlib.pyplot as plt import time# 创建一个空列表&#xff0c;用于存储负载数据 load_data []# 循环收集负载数据 while True:# 获取当前系统负载load_avg psutil.getloadavg()# 将平均负载添加到load_data列表中load_da…