C++设计模式——命令模式 Command


命令模式,是将一个请求封装为一个对象,从而使我们可以用不同的请求对客户进行参数化、对请求排队或记录请求日志,以及支持可撤销的操作。

动机(Motivation)

  • ”行为请求者“与”行为实现者“通常呈现一种”紧耦合“。但在某些场合——比如需要对行为进行”记录、撤销、事务“等处理,这种无法抵御变化的紧耦合是不合适的。
  • 在这种情况下,如何将”行为请求者“与”行为实现者“解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

模式定义

将一个请求(行为)封装成一个对象,从而使你可用不用的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。 ——《设计模式》GoF

要点总结

  • Command模式的根本目的在于将”行为请求者“与”行为实现者“解耦,在面向对象语言中,常见的实现手段是”将行为抽象为对象“。
  • 与C++中的函数对象类似,C++函数对象以函数签名来定义行为接口规范,更灵活性能更高。

结构(Structure)

基本代码

#include 
#include 
using namespace std;

// Receiver类,知道如何实施与执行一个与请求相关的操作:
class Receiver {
public:
    void Action() {
        cout << "Receiver" << endl;
    }
};

// Command类,用来声明执行操作的接口
class Command {
public:
    virtual void Excute() = 0;
    virtual void setReceiver(Receiver* r) = 0;
    virtual ~Command(){};
};

// ConcreteCommand类,绑定一个Receiver,调用其相应操作以实现Excute:
class ConcreteCommand : public Command {
private:
    Receiver* receiver;
public:
    void setReceiver(Receiver* r) {
        receiver = r;
    }
    void Excute() {
        //cout << "ConcreteCommand" << endl;
        receiver->Action();
    }
};

// 要求该命令执行这个请求:
class Invoker {
private:
    list commands;
public:
    void setCommand(Command* c) {
        commands.push_back(c);
    }
    void Notify() {
        for (auto c = commands.begin(); c != commands.end(); c++) {
            (*c)->Excute();
        }
    }
};

// 客户端实现代码:
int main() {
    Command* c = new ConcreteCommand();
    Receiver* r = new Receiver();
    c->setReceiver(r);

    Invoker I;
    i.setCommand(c);
    i.Notify();   // Receiver

    delete r;
    delete c;

    return 0;
}

应用场景

优点:

  • 能较容易地设计一个命令队列;
  • 在需要的情况下,可以较容易地将命令记入日志;
  • 允许接收请求的一方决定是否要否决请求;
  • 可以轻易地实现对请求的撤销和重做;
  • 增加新的具体命令类很方便;
  • 命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开了。