命 令 模 式

什么是命令模式?

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

引出:

在市中心逛了很久的街后, 你找到了一家不错的餐厅, 坐在了临窗的座位上。 一名友善的服务员走近你, 迅速记下你点的食物, 写在一张纸上。 服务员来到厨房, 把订单贴在墙上。 过了一段时间, 厨师拿到了订单, 他根据订单来准备食物。 厨师将做好的食物和订单一起放在托盘上。 服务员看到托盘后对订单进行检查, 确保所有食物都是你要的, 然后将食物放到了你的桌上。

那张纸就是一个命令, 它在厨师开始烹饪前一直位于队列中。 命令中包含与烹饪这些食物相关的所有信息。 厨师能够根据它马上开始烹饪, 而无需跑来直接和你确认订单详情。

介绍

意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

何时使用:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

如何解决:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。

关键代码:定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口。

命令模式的基本结构

命令模式的核心思想是将“请求”封装为对象,使得可以用不同的请求对客户进行参数化。这样做的好处是能够轻松地扩展和更改系统中的命令,而不影响调用者。

命令模式主要包括以下几个组件:

  1. 命令接口(Command Interface):定义执行操作的接口。
  2. 具体命令(Concrete Command):实现命令接口,执行具体的操作。
  3. 调用者(Invoker):调用命令对象执行请求。
  4. 接收者(Receiver):执行请求的具体操作。

示例代码:

1. 命令接口

首先,我们定义一个命令接口,所有的具体命令都将实现这个接口:

#include <iostream>
#include <memory>

// Command接口
class Command {
public:
    virtual ~Command() = default;
    virtual void execute() = 0;
    virtual void undo() = 0;
};

2. 具体命令

然后,我们实现几个具体的命令,比如打开和关闭灯光的命令:

// 接收者类:灯
class Light {
public:
    void on() {
        std::cout << "Light is ON" << std::endl;
    }
    void off() {
        std::cout << "Light is OFF" << std::endl;
    }
};

// 具体命令类:打开灯
class LightOnCommand : public Command {
public:
    explicit LightOnCommand(std::shared_ptr<Light> light) : light_(light) {}

    void execute() override {
        light_->on();
    }

    void undo() override {
        light_->off();
    }

private:
    std::shared_ptr<Light> light_;
};

// 具体命令类:关闭灯
class LightOffCommand : public Command {
public:
    explicit LightOffCommand(std::shared_ptr<Light> light) : light_(light) {}

    void execute() override {
        light_->off();
    }

    void undo() override {
        light_->on();
    }

private:
    std::shared_ptr<Light> light_;
};

3. 调用者

接下来,我们实现一个调用者类,用于执行命令:

// 调用者类:遥控器
class RemoteControl {
public:
    void setCommand(std::shared_ptr<Command> command) {
        command_ = command;
    }

    void pressButton() {
        if (command_) {
            command_->execute();
        }
    }

    void pressUndo() {
        if (command_) {
            command_->undo();
        }
    }

private:
    std::shared_ptr<Command> command_;
};

4. 客户端代码

最后,我们编写客户端代码来测试我们的命令模式实现:

int main() {
    auto light = std::make_shared<Light>();

    auto lightOnCommand = std::make_shared<LightOnCommand>(light);
    auto lightOffCommand = std::make_shared<LightOffCommand>(light);

    RemoteControl remote;

    // 打开灯
    remote.setCommand(lightOnCommand);
    remote.pressButton();  // 输出: Light is ON

    // 撤销打开灯操作
    remote.pressUndo();    // 输出: Light is OFF

    // 关闭灯
    remote.setCommand(lightOffCommand);
    remote.pressButton();  // 输出: Light is OFF

    // 撤销关闭灯操作
    remote.pressUndo();    // 输出: Light is ON

    return 0;
}

优点

1. 解耦调用者和接收者

命令模式将请求的发出者和执行者解耦,使得调用者只需要知道如何发出请求,而不需要知道请求是如何被执行的。这种解耦提高了代码的灵活性和可维护性。

2. 增强可扩展性

新的命令可以很容易地添加到系统中,而不需要修改现有代码。这使得系统具有良好的扩展性。例如,可以轻松地添加新的家电控制命令,而不影响现有的遥控器实现。

3. 支持撤销和重做操作

通过在命令对象中实现 undo 方法,命令模式可以方便地支持撤销和重做操作。这对于需要复杂操作的应用(如编辑器、事务性系统)非常有用。

4. 支持组合命令

命令模式可以将多个命令组合成一个宏命令,从而实现一系列操作的封装。这使得批处理操作变得简单。

5. 支持日志记录和回放

可以将命令对象记录下来,以支持日志记录和回放操作。通过这种方式,可以实现操作的重放和审计。

缺点

1. 增加类的数量

引入命令模式通常会增加系统中类的数量,因为每个具体操作都需要一个对应的命令类。这可能会使系统变得复杂,尤其是在操作非常多的情况下。

2. 引用开销

每个命令对象都需要保存接收者的引用,这可能会增加内存开销,特别是在命令对象数量较多时。

3. 简单命令的过度设计

对于一些简单操作,使用命令模式可能显得过于复杂和冗余。例如,对于仅仅调用一个方法的简单操作,命令模式可能增加了不必要的复杂性。

4. 需要额外的逆操作逻辑

为了支持撤销操作,每个命令都需要实现 undo 方法,这可能需要维护额外的状态和逻辑,增加了实现的复杂性。

示例扩展

为了更好地理解这些优缺点,我们可以考虑扩展前面的遥控器示例,增加一个组合命令(宏命令)和撤销功能。

组合命令(宏命令)

组合命令可以将多个命令组合在一起,使得它们可以被一起执行或撤销:

#include <vector>

// 宏命令类
class MacroCommand : public Command {
public:
    void addCommand(std::shared_ptr<Command> command) {
        commands_.push_back(command);
    }

    void execute() override {
        for (const auto& command : commands_) {
            command->execute();
        }
    }

    void undo() override {
        for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) {
            (*it)->undo();
        }
    }

private:
    std::vector<std::shared_ptr<Command>> commands_;
};

示例客户端代码

int main() {
    auto light = std::make_shared<Light>();

    auto lightOnCommand = std::make_shared<LightOnCommand>(light);
    auto lightOffCommand = std::make_shared<LightOffCommand>(light);

    RemoteControl remote;

    // 创建宏命令
    auto macroCommand = std::make_shared<MacroCommand>();
    macroCommand->addCommand(lightOnCommand);
    macroCommand->addCommand(lightOffCommand);

    // 执行宏命令
    remote.setCommand(macroCommand);
    remote.pressButton();  // 输出: Light is ON, Light is OFF

    // 撤销宏命令
    remote.pressUndo();    // 输出: Light is ON, Light is OFF

    return 0;
}

通过这种扩展,我们可以看到命令模式的灵活性和扩展性如何在实际应用中体现。同时,我们也可以理解在更复杂的场景中,命令模式如何增加系统的复杂性和类的数量。

总结

通过以上示例,我们展示了命令模式如何将请求封装为对象,并通过这种方式实现了灵活的请求处理。命令模式使得请求的发出者与执行者解耦,增强了系统的扩展性和可维护性。

命令模式非常适用于以下场景:

  • 需要对请求排队并执行。
  • 需要支持撤销操作。
  • 需要将一组操作封装为对象,并能进行参数化处理。

在实际开发中,命令模式可以用来实现诸如事务性操作、日志记录、宏命令等功能,是一种非常实用的设计模式。

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值