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

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

文章目录

      • 命令模式的详细介绍
      • 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.mrgr.cn/news/81480.html

相关文章:

  • 【EtherCATBasics】- KRTS C++示例精讲(2)
  • Qt父窗口处理子窗口大小变化消息installEventFilter
  • 大型语言模型(LLMs)演化树 Large Language Models
  • 2.系统学习-逻辑回归
  • 2025差旅平台怎么选?一体化、全流程降本案例解析
  • 【MinIO系列】MinIO Client (mc) 完全指南
  • cannot import name ‘_C‘ from ‘pytorch3d‘
  • 骑砍2霸主MOD开发(26)-Mono脚本系统
  • More Effective C++之技术Techniques,Idioms,Patterns_条款26-27
  • 【Hot100刷题计划】Day04 栈专题 1~3天回顾(持续更新)
  • 细说STM32F407单片机通过IIC读写EEPROM 24C02
  • 【ES6复习笔记】Spread 扩展运算符(8)
  • 基础运维学习计划-base版
  • 【golang】map遍历注意事项
  • 【ES6复习笔记】解构赋值(2)
  • 知识碎片-环境配置
  • Es搭建——单节点——Linux
  • 【ES6复习笔记】Map(14)
  • 常规配置、整合IDEA
  • Android 常用三方库
  • 硬件模块常使用的外部中断及中断优先级
  • ESP32_H2(IDF)学习系列-ADC模数转换(连续转换)
  • Python:模拟(包含例题:饮料换购 图像模糊 螺旋矩阵)
  • (长期更新)《零基础入门 ArcGIS(ArcMap) 》实验五----土地整治(超超超详细!!!)
  • YOLOv10目标检测-训练自己的数据
  • JS进阶-手写Promise