专业描述
命令模式是一种行为设计模式, 它可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。
真实世界类比
在市中心逛了很久的街后, 你找到了一家不错的餐厅, 坐在了临窗的座位上。 一名友善的服务员走近你, 迅速记下你点的食物, 写在一张纸上。 服务员来到厨房, 把订单贴在墙上。 过了一段时间, 厨师拿到了订单, 他根据订单来准备食物。 厨师将做好的食物和订单一起放在托盘上。 服务员看到托盘后对订单进行检查, 确保所有食物都是你要的, 然后将食物放到了你的桌上。
那张纸就是一个命令, 它在厨师开始烹饪前一直位于队列中。 命令中包含与烹饪这些食物相关的所有信息。 厨师能够根据它马上开始烹饪, 而无需跑来直接和你确认订单详情。
命令模式结构
-
发送者 (Sender)——亦称 “触发者 (Invoker)”——类负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用。 发送者触发命令, 而不向接收者直接发送请求。 注意, 发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令。
-
命令 (Command) 接口通常仅声明一个执行命令的方法。
-
具体命令 (Concrete Commands) 会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码, 这些类可以进行合并。
- 接收对象执行方法所需的参数可以声明为具体命令的成员变量。 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化。
-
接收者 (Receiver) 类包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作。
-
客户端 (Client) 会创建并配置具体命令对象。 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。 此后, 生成的命令就可以与一个或多个发送者相关联了。
命令模式适合应用场景
- 如果你需要通过操作来参数化对象, 可使用命令模式。
- 如果你想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式。
- 如果你想要实现操作回滚功能, 可使用命令模式。
实现方式
-
声明仅有一个执行方法的命令接口。
-
抽取请求并使之成为实现命令接口的具体命令类。 每个类都必须有一组成员变量来保存请求参数和对于实际接收者对象的引用。 所有这些变量的数值都必须通过命令构造函数进行初始化。
-
找到担任发送者职责的类。 在这些类中添加保存命令的成员变量。 发送者只能通过命令接口与其命令进行交互。 发送者自身通常并不创建命令对象, 而是通过客户端代码获取。
-
修改发送者使其执行命令, 而非直接将请求发送给接收者。
-
客户端必须按照以下顺序来初始化对象:
- 创建接收者。
- 创建命令, 如有需要可将其关联至接收者。
- 创建发送者并将其与特定命令关联。
C++代码示例:
/**
* The Command interface declares a method for executing a command.
*/
class Command {
public:
virtual ~Command() {
}
virtual void Execute() const = 0;
};
/**
* Some commands can implement simple operations on their own.
*/
class SimpleCommand : public Command {
private:
std::string pay_load_;
public:
explicit SimpleCommand(std::string pay_load) : pay_load_(pay_load) {
}
void Execute() const override {
std::cout << "SimpleCommand: See, I can do simple things like printing (" << this->pay_load_ << ")\n";
}
};
/**
* The Receiver classes contain some important business logic. They know how to
* perform all kinds of operations, associated with carrying out a request. In
* fact, any class may serve as a Receiver.
*/
class Receiver {
public:
void DoSomething(const std::string &a) {
std::cout << "Receiver: Working on (" << a << ".)\n";
}
void DoSomethingElse(const std::string &b) {
std::cout << "Receiver: Also working on (" << b << ".)\n";
}
};
/**
* However, some commands can delegate more complex operations to other objects,
* called "receivers."
*/
class ComplexCommand : public Command {
/**
* @var Receiver
*/
private:
Receiver *receiver_;
/**
* Context data, required for launching the receiver's methods.
*/
std::string a_;
std::string b_;
/**
* Complex commands can accept one or several receiver objects along with any
* context data via the constructor.
*/
public:
ComplexCommand(Receiver *receiver, std::string a, std::string b) : receiver_(receiver), a_(a), b_(b) {
}
/**
* Commands can delegate to any methods of a receiver.
*/
void Execute() const override {
std::cout << "ComplexCommand: Complex stuff should be done by a receiver object.\n";
this->receiver_->DoSomething(this->a_);
this->receiver_->DoSomethingElse(this->b_);
}
};
/**
* The Invoker is associated with one or several commands. It sends a request to
* the command.
*/
class Invoker {
/**
* @var Command
*/
private:
Command *on_start_;
/**
* @var Command
*/
Command *on_finish_;
/**
* Initialize commands.
*/
public:
~Invoker() {
delete on_start_;
delete on_finish_;
}
void SetOnStart(Command *command) {
this->on_start_ = command;
}
void SetOnFinish(Command *command) {
this->on_finish_ = command;
}
/**
* The Invoker does not depend on concrete command or receiver classes. The
* Invoker passes a request to a receiver indirectly, by executing a command.
*/
void DoSomethingImportant() {
std::cout << "Invoker: Does anybody want something done before I begin?\n";
if (this->on_start_) {
this->on_start_->Execute();
}
std::cout << "Invoker: ...doing something really important...\n";
std::cout << "Invoker: Does anybody want something done after I finish?\n";
if (this->on_finish_) {
this->on_finish_->Execute();
}
}
};
/**
* The client code can parameterize an invoker with any commands.
*/
int main() {
Invoker *invoker = new Invoker;
invoker->SetOnStart(new SimpleCommand("Say Hi!"));
Receiver *receiver = new Receiver;
invoker->SetOnFinish(new ComplexCommand(receiver, "Send email", "Save report"));
invoker->DoSomethingImportant();
delete invoker;
delete receiver;
return 0;
}
output:
Invoker: Does anybody want something done before I begin?
SimpleCommand: See, I can do simple things like printing (Say Hi!)
Invoker: ...doing something really important...
Invoker: Does anybody want something done after I finish?
ComplexCommand: Complex stuff should be done by a receiver object.
Receiver: Working on (Send email.)
Receiver: Also working on (Save report.)