【软件设计模式之命令模式】

文章目录

  • 一、命令模式简介
    • [1. 定义](#1. 定义)
    • [2. 核心概念](#2. 核心概念)
      • [a. 命令(Command)](#a. 命令(Command))
      • [b. 接收者(Receiver)](#b. 接收者(Receiver))
      • [c. 调用者(Invoker)](#c. 调用者(Invoker))
      • [d. 客户端(Client)](#d. 客户端(Client))
  • 二、命令模式的实际应用
    • [1. 命令模式的优点](#1. 命令模式的优点)
      • [a. 解耦发起者和执行者](#a. 解耦发起者和执行者)
      • [b. 易于扩展](#b. 易于扩展)
      • [c. 组合命令](#c. 组合命令)
      • [d. 支持撤销操作](#d. 支持撤销操作)
      • [e. 可以实现请求的排队和日志记录](#e. 可以实现请求的排队和日志记录)
    • [2. 命令模式的缺点](#2. 命令模式的缺点)
      • [a. 可能导致类数量增多](#a. 可能导致类数量增多)
      • [b. 增加代码量和复杂性](#b. 增加代码量和复杂性)
    • [3. 适用场景](#3. 适用场景)
      • [a. 需要参数化和延迟执行操作时](#a. 需要参数化和延迟执行操作时)
      • [b. 支持撤销和重做操作](#b. 支持撤销和重做操作)
      • [c. 需要实现操作的日志记录和恢复功能](#c. 需要实现操作的日志记录和恢复功能)
      • [d. 需要处理事务](#d. 需要处理事务)
  • 三、命令模式的实现
    • [1. 代码示例](#1. 代码示例)
    • [2. 实现步骤](#2. 实现步骤)
      • [a. 定义命令接口](#a. 定义命令接口)
      • [b. 创建具体命令类](#b. 创建具体命令类)
      • [c. 定义接收者](#c. 定义接收者)
      • [d. 实现调用者](#d. 实现调用者)
    • [3. 案例分析](#3. 案例分析)
  • 四、命令模式与其他设计模式的比较
    • [1. 命令模式与策略模式](#1. 命令模式与策略模式)
      • [a. 相似点](#a. 相似点)
      • [b. 不同点](#b. 不同点)
    • [2. 命令模式与观察者模式](#2. 命令模式与观察者模式)
      • [a. 相似点](#a. 相似点)
      • [b. 不同点](#b. 不同点)

一、命令模式简介

命令模式是一种行为设计模式,它在软件开发中扮演着特殊的角色,尤其是在处理操作请求、排队请求、记录日志,以及支持可撤销操作方面。

1. 定义

命令模式将请求封装成对象,从而允许使用者与接收者解耦,使用不同的请求、队列或日志来参数化其他对象。它也支持可撤销操作。简单来说,命令模式把一个请求或简单操作封装到一个对象中。

在命令模式中,这个封装包含了所有必要的信息,这可能包括调用方法的名称、拥有该方法的对象、方法参数的值等。

2. 核心概念

a. 命令(Command)

命令对象为所有命令声明一个接口。在最简单的形式中,这个接口包含了一个执行操作的方法。命令对象知道接收者是谁以及执行哪些操作。

b. 接收者(Receiver)

接收者是命令操作的对象。它知道如何执行与请求相关的操作。任何类都可以作为接收者。

c. 调用者(Invoker)

调用者持有一个命令对象,并在某一时间点调用命令对象的执行方法,以发送请求。调用者不需要知道请求是如何执行的,也不知道操作的具体细节。

d. 客户端(Client)

客户端负责创建一个具体的命令,并设置其接收者。客户端可以决定哪些命令执行何时执行。

二、命令模式的实际应用

1. 命令模式的优点

a. 解耦发起者和执行者

命令模式最显著的好处是将发起请求的对象(调用者)与执行请求的对象(接收者)解耦。这种分离使得调用者不需要知道请求的具体实现细节。

b. 易于扩展

命令模式允许轻松地添加新命令,因为新增命令只需实现一个接口。这有助于遵循开闭原则,即软件实体应该对扩展开放,对修改关闭。

c. 组合命令

可以组合多个命令,实现复杂的功能。例如,可以实现宏命令,这是一种复合命令,它包含多个子命令。

d. 支持撤销操作

由于每个操作都被封装在命令对象中,可以很方便地实现撤销(undo)和重做(redo)功能。

e. 可以实现请求的排队和日志记录

命令可以排队执行,也可以记录日志,有助于实现事务功能,如对失败的操作进行回滚。

2. 命令模式的缺点

a. 可能导致类数量增多

每个新命令可能都需要创建一个新类,随着应用程序中命令数量的增加,会增加系统的复杂性。

b. 增加代码量和复杂性

对于一些简单的操作,使用命令模式可能会让代码变得不必要地复杂,增加代码量。

3. 适用场景

a. 需要参数化和延迟执行操作时

当需要将操作封装成对象,以便将其传递、存储或操作时,命令模式非常适用。

b. 支持撤销和重做操作

在需要提供撤销和重做功能的场景中,如文本编辑器或IDE中的操作,命令模式非常有用。

c. 需要实现操作的日志记录和恢复功能

在需要记录操作历史以便后续恢复或重放操作的系统中,命令模式是一个理想的选择。

d. 需要处理事务

在需要创建复杂的事务系统,如需要维护操作顺序和状态的数据库管理系统中,命令模式可以帮助实现事务的回滚机制。

三、命令模式的实现

命令模式的实现涉及到定义命令接口、创建具体命令类、定义接收者和调用者。

1. 代码示例

一个简单的文本编辑器应用,实现一个文本添加和撤销的功能。

c 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <memory>

// 命令接口
class Command {
public:
    virtual ~Command() {}
    virtual void Execute() = 0;
    virtual void Undo() = 0;
};

// 接收者类
class TextEditor {
    std::string text;
public:
    void addText(const std::string& newText) {
        text += newText;
    }
    void removeText(size_t length) {
        text.erase(text.size() - length);
    }
    void showText() {
        std::cout << text << std::endl;
    }
};

// 具体命令类
class AddTextCommand : public Command {
    TextEditor& editor;
    std::string textToAdd;
public:
    AddTextCommand(TextEditor& editor, const std::string& text) : editor(editor), textToAdd(text) {}
    void Execute() override {
        editor.addText(textToAdd);
    }
    void Undo() override {
        editor.removeText(textToAdd.length());
    }
};

// 调用者类
class CommandInvoker {
    std::vector<std::shared_ptr<Command>> history;
public:
    void executeCommand(std::shared_ptr<Command> command) {
        command->Execute();
        history.push_back(command);
    }
    void undo() {
        if (!history.empty()) {
            history.back()->Undo();
            history.pop_back();
        }
    }
};

int main() {
    TextEditor editor;
    CommandInvoker invoker;

    invoker.executeCommand(std::make_shared<AddTextCommand>(editor, "Hello"));
    invoker.executeCommand(std::make_shared<AddTextCommand>(editor, " World"));

    editor.showText(); // 输出: Hello World

    invoker.undo();
    editor.showText(); // 输出: Hello

    invoker.undo();
    editor.showText(); // 输出: (空)

    return 0;
}

2. 实现步骤

a. 定义命令接口

首先,创建一个命令接口(Command),定义执行和撤销命令的方法。

b. 创建具体命令类

然后,为每个具体的动作实现一个命令类(如AddTextCommand),这些类继承自命令接口并实现相应的方法。

c. 定义接收者

接收者是命令执行的对象(如TextEditor),它知道如何实际执行命令。

d. 实现调用者

调用者(如CommandInvoker)负责调用命令的执行方法,并可以存储历史记录,用于实现撤销功能。

3. 案例分析

通过AddTextCommand,用户可以向文本编辑器中添加文本。编辑器的状态可以通过调用命令的Undo方法来回退。在更复杂的应用中,可以扩展这种模式来实现更多复杂的命令和功能,如复制、粘贴、删除等。

四、命令模式与其他设计模式的比较

1. 命令模式与策略模式

a. 相似点

  • 封装行为:两者都涉及到将行为封装在对象中。
  • 可交换性:在这两种模式中,可以动态地改变对象所封装的行为。

b. 不同点

  • 目的和用途

    • 命令模式:重点在于分离发起命令的对象(调用者)和接收命令的对象(接收者)。它允许将命令封装为对象以进行存储、传递和执行。
    • 策略模式:侧重于使算法的变体可以互换使用。它允许根据上下文更改对象的行为,而不是通过封装命令和请求。
  • 实现方式

    • 命令模式中,调用者通常不知道命令具体实施的操作,只是知道如何发出命令。
    • 策略模式中,上下文类知道哪个策略正在使用,并直接使用它来完成其任务。

2. 命令模式与观察者模式

a. 相似点

  • 解耦:两者都有助于解耦对象,使得发起动作的对象不必关心接收动作的对象。

b. 不同点

  • 通信机制

    • 命令模式:强调在对象之间传递封装有操作细节的命令对象。命令模式更多地关注于操作和它的发送者和接收者。
    • 观察者模式:定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。观察者模式更多地用于事件处理和通知机制。
  • 使用场景

    • 使用命令模式,当需要将请求或简单操作封装到对象中,以参数化其他对象,实现撤销操作或者将请求放入队列中处理时。
    • 使用观察者模式,当一个状态的改变需要自动通知一个或多个对象,并且对象间的这种交互是松散耦合的时。
相关推荐
小白不太白9508 小时前
设计模式之 模板方法模式
java·设计模式·模板方法模式
色空大师8 小时前
23种设计模式
java·开发语言·设计模式
闲人一枚(学习中)8 小时前
设计模式-创建型-建造者模式
java·设计模式·建造者模式
ZZZCY20039 小时前
华为ENSP--IP编址及静态路由配置
网络·华为
博风9 小时前
设计模式:6、装饰模式(包装器)
设计模式
A_cot9 小时前
理解设计模式与 UML 类图:构建稳健软件架构的基石
microsoft·设计模式·简单工厂模式·工厂方法模式·uml
君败红颜9 小时前
设计模式之创建模式篇
设计模式
jikuaidi6yuan11 小时前
鸿蒙系统(HarmonyOS)分布式任务调度
分布式·华为·harmonyos
闲人一枚(学习中)12 小时前
设计模式-创建型-抽象工厂模式
设计模式·抽象工厂模式
小白不太白95014 小时前
设计模式之 观察者模式
观察者模式·设计模式