C++ 设计模式之命令模式

【声明】本题目来源于卡码网(题目页面 (kamacoder.com))
【提示:如果不想看文字介绍,可以直接跳转到C++编码部分】


【设计模式大纲】


【简介】

-- 什么是命令模式(第15种模式)

命令模式是⼀种**⾏为型设计模式** ,其允许将请求封装成⼀个对象(命令对象,包含执⾏操作所需的所有信息),并将命令对象按照⼀定的顺序存储在队列中 ,然后再逐⼀调用执⾏ ,这些命令也可以**⽀持反向操作,进⾏撤销和重做** 。

这样⼀来,发送者只需要触发命令就可以完成操作,不需要知道接受者的具体操作,从⽽实现两者间的解耦

举个现实中的应⽤场景,遥控器可以控制不同的设备,在命令模式中,可以假定每个按钮都是⼀个命令对象,包含执⾏特定操作的命令,不同设备对同⼀命令的具体操作也不同,这样就可以⽅便的添加设备和命令对象。


【基本结构】

命令模式包含以下⼏个基本⻆⾊:

  • 命令接⼝Command:接⼝或者抽象类,定义执⾏操作的接⼝。
  • 具体命令类ConcreteCommand : 实现命令接⼝,执⾏具体操作,在调⽤execute ⽅法时使"接收者对象"根据命令完成具体的任务,⽐如遥控器中的"开机","关机"命令。
  • 接收者类Receiver : 接受并执⾏命令的对象,可以是任何对象,遥控器可以控制空调,也可以控制电视机,电视机和空调负责执⾏具体操作,是接收者。
  • 调⽤者类Invoker: 发起请求的对象,有⼀个将命令作为参数传递的⽅法。它不关⼼命令的具体实现,只负责调⽤命令对象的 execute() ⽅法来传递请求,在本例中,控制遥控器的"⼈"就是调⽤者。
  • 客户端:创建具体的命令对象和接收者对象,然后将它们组装起来。

【简易实现】

以Java代码先作以说明:

1. 定义执⾏操作的接⼝ :包含⼀个execute ⽅法。有的时候还会包括unExecute ⽅法,表示撤销命令。

java 复制代码
public interface Command {
    void execute();
}

2. 定义接受者类,知道如何实施与执⾏⼀个请求相关的操作。

java 复制代码
public class Receiver {
    public void action() {
        // 执⾏操作
    }
}

3. 实现命令接⼝,执⾏具体的操作。

java 复制代码
public class ConcreteCommand implements Command {
    // 接收者对象
    private Receiver receiver;
    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        // 调⽤接收者相应的操作
    receiver.action();
    }
}

4.

(1)定义调用者类,调⽤命令对象执⾏请求。

java 复制代码
public class Invoker {
    private Command command;
    public Invoker(Command command) {
        this.command = command;
    }
    public void executeCommand() {
        command.execute();
    }
}

(2)调⽤者类中可以维护⼀个命令队列或者"撤销栈",以⽀持批处理和撤销命令。

java 复制代码
/**
* @version Copyright (c) 2024 NCDC, Servo。 Unpublished - All rights reserved
* @file CommandMode
* @brief 命令模式
* @autor 写代码的小恐龙er
* @date 2024/01/17
*/

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
// 调⽤者类:命令队列和撤销请求
class Invoker {
    private Queue<Command> commandQueue; // 命令队列
    private Stack<Command> undoStack; // 撤销栈
    public Invoker() {
        this.commandQueue = new LinkedList<>();
        this.undoStack = new Stack<>();
    }
    // 设置命令并执⾏
    public void setAndExecuteCommand(Command command) {
        command.execute();
        commandQueue.offer(command);
        undoStack.push(command);
    }
    // 撤销上⼀个命令
    public void undoLastCommand() {
        if (!undoStack.isEmpty()) {
        Command lastCommand = undoStack.pop();
        lastCommand.undo(); // 需要命令类实现 undo ⽅法
        commandQueue.remove(lastCommand);
        } else {
            System.out.println("No command to undo.");
        }
    }
    // 执⾏命令队列中的所有命令
    public void executeCommandsInQueue() {
        for (Command command : commandQueue) {
        command.execute();
        }
    }
}

5.客户端使用,创建具体的命令对象和接收者对象,然后进⾏组装。

java 复制代码
public class Main {
    public static void main(String[] args) {
        Receiver receiver = new Receiver();
        Command command = new ConcreteCommand(receiver);
        Invoker invoker = new Invoker(command);
        invoker.executeCommand();
    }
}

【优缺点和使用场景】

命令模式在需要将请求封装成对象、⽀持撤销和重做、设计命令队列等情况下,都是⼀个有效的设计模式。

  • 撤销操作: 需要⽀持撤销操作,命令模式可以存储历史命令,轻松实现撤销功能。
  • 队列请求: 命令模式可以将请求排队,形成⼀个命令队列,依次执⾏命令。
  • 可扩展性: 可以很容易地添加新的命令类和接收者类,而不影响现有的代码。新增命令不需要修改现有代码,符合开闭原则

但是对于每个命令,都会有⼀个具体命令类,这可能导致类的数量急剧增加,增加了系统的复杂性。

命令模式同样有着很多现实场景的应用,⽐如Git中的很多操作,如提交(commit)、合并(merge)等,都可以看作是命令模式的应⽤,用户通过执⾏相应的命令来操作版本库。Java的GUI编程中,很多事件处理机制也都使⽤了命令模式。例如,每个按钮都有⼀个关联的 Action ,它代表⼀个命令,按钮的点击触发 Action 的执⾏。


【C++编码部分】

1. 题目描述

小明去奶茶店买奶茶,他可以通过在自助点餐机上来点不同的饮品,请你使用命令模式设计一个程序,模拟这个自助点餐系统的功能。

2. 输入描述

  • 第一行是一个整数 n(1 ≤ n ≤ 100),表示点单的数量。
  • 接下来的 n 行,每行包含一个字符串,表示点餐的饮品名称。

3. 输出描述

输出执行完所有点单后的制作情况,每行输出一种饮品的制作情况。如果制作完成,输出 "XXX is ready!",其中 XXX 表示饮品名称。

4. C++编码实例

cpp 复制代码
/**
* @version Copyright (c) 2024 NCDC, Servo。 Unpublished - All rights reserved
* @file CommandMode.hpp
* @brief 命令模式
* @autor 写代码的小恐龙er
* @date 2024/01/17
*/

#include <iostream>
#include <string>
#include <vector>

using namespace std;

// 前置声明

// 命令接口类
class Command;
// 具体命令类 -- 点餐命令
class OrderCommand;
// 接收者类
class DrinkMaker;
// 调用者类
class OrderMachine;

// 实现
// 1. 命令接口类
class Command
{
public:
    // 接口函数
    virtual void Execute() = 0;
};

// 2. 接收者类
class DrinkMaker {
// 成员函数
public:
    void makeDrink(string drinkName) {
        std::cout << drinkName << " is ready!" << endl;
    }
};

// 3. 具体命令类 -- 点餐命令
class OrderCommand : public Command
{
// 成员数据
private:
    string _drinkNmae;
    DrinkMaker *_receiver;
// 成员函数
public:
    // 构造函数
    OrderCommand(string drinkNmae, DrinkMaker *receiver){
        this->_drinkNmae = drinkNmae;
        this->_receiver = receiver;
    }
    // 重载命令执行函数
    void Execute() override{
        _receiver->makeDrink(_drinkNmae);
    }
};

// 4. 调用者类
class OrderMachine
{
// 成员数据
private:
    // 调用的命令基类
    Command *_command;
// 成员函数
public:
    OrderMachine(Command *command){
        this->_command = command;
    }
    // 调用命令的执行
    void ExecuteOrder(){
        if(_command != nullptr){
            _command->Execute();
        }
        return;
    }
};

// 客户端
int main()
{
    // 点单数量
    int orderNum = 0;
    std::cin >> orderNum;
    
    // 创建命令基类
    Command *command = nullptr;
    // 创建调用者类  后续进行堆区赋值
    OrderMachine *orderMachine = nullptr;
    // 创建接收者类
    DrinkMaker *drinkMaker = new DrinkMaker();
    // 遍历
    for(int i = 0; i < orderNum; i++){
        // 饮品名称
        string name;
        std::cin >> name;
        
        // 构造类
        command = new OrderCommand(name, drinkMaker);
        orderMachine = new OrderMachine(command);
        
        // 执行命令
        orderMachine->ExecuteOrder();
    }
    
    // 析构
    delete drinkMaker;
    drinkMaker = nullptr;
    
    if(command != nullptr){
        delete command;
        command = nullptr;
    }
    
    if(orderMachine != nullptr){
        delete orderMachine;
        orderMachine = nullptr;
    }
    return 0;
}


......

To be continued.

相关推荐
charlie11451419111 分钟前
如何使用Qt创建一个浮在MainWindow上的滑动小Panel
开发语言·c++·qt·界面设计
Dcs15 分钟前
VSCode等多款主流 IDE 爆出安全漏洞!插件“伪装认证”可执行恶意命令!
java
神仙别闹19 分钟前
基于Python实现LSTM对股票走势的预测
开发语言·python·lstm
保持学习ing21 分钟前
day1--项目搭建and内容管理模块
java·数据库·后端·docker·虚拟机
京东云开发者32 分钟前
Java的SPI机制详解
java
超级小忍1 小时前
服务端向客户端主动推送数据的几种方法(Spring Boot 环境)
java·spring boot·后端
程序无bug1 小时前
Spring IoC注解式开发无敌详细(细节丰富)
java·后端
小莫分享1 小时前
Java Lombok 入门
java
程序无bug1 小时前
Spring 对于事务上的应用的详细说明
java·后端
食亨技术团队1 小时前
被忽略的 SAAS 生命线:操作日志有多重要
java·后端