当前位置: 首页 > article >正文

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

文章目录

      • 命令模式的详细介绍
      • 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.kler.cn/a/449502.html

相关文章:

  • Spring Boot 中的 @Scheduled 定时任务以及开关控制
  • 串口通信控制LED灯
  • 初学stm32 --- NVIC中断
  • Java设计模式 —— 【结构型模式】外观模式详解
  • 深入了解蓝牙Profile类型与设备的对应关系
  • 安卓从Excel文件导入数据到SQLite数据库的实现
  • 安卓APP-HTTPS抓包Frida Hook教程
  • 集星云推短视频矩阵系统:重塑短视频营销格局
  • 图匹配经典论文(三)Deep Learning of Graph Matching—CVPR2018图匹配
  • C++中的模板元编程
  • 0基础学前端-----CSS DAY5
  • 004最长回文子串
  • ABAQUS纤维混凝土冲击破坏三维模型
  • 苏黎世联邦理工学院与加州大学伯克利分校推出MaxInfoRL:平衡内在与外在探索的全新强化学习框架
  • C++ 中的多线程与并发编程:从基础到进阶
  • Apache RocketMQ 5.1.3安装部署文档
  • QT多媒体开发(一):概述
  • 数据流图和流程图的区别
  • Vue.js 表单处理
  • 3.1、SDH的5种标准容器
  • CentOS常见命令
  • Note2024122001_Excel按成绩排名
  • 【YashanDB知识库】insert语句有编码不识别字,执行卡住问题
  • 掌握命令行参数的艺术:Python的`argparse`库
  • Java 连接 FTP 服务器全解析
  • 35道面向初中级前端的基础面试题