命令模式

命令模式

使用场景

问题 😟

假如你正在开发一款新的文字编辑器, 当前的任务是创建一个包含多个按钮的工具栏, 并让每个按钮对应编辑器的不同操作。 你创建了一个非常简洁的 按钮类, 它不仅可用于生成工具栏上的按钮, 还可用于生成各种对话框的通用按钮。尽管所有按钮看上去都很相似, 但它们可以完成不同的操作 (打开、 保存、 打印和应用等)。 你会在哪里放置这些按钮的点击处理代码呢? 最简单的解决方案是在使用按钮的每个地方都创建大量的子类。 这些子类中包含按钮点击后必须执行的代码。

大量的按钮子类

你很快就意识到这种方式有严重缺陷。 首先, 你创建了大量的子类, 当每次修改基类 按钮时, 你都有可能需要修改所有子类的代码。 简单来说, GUI 代码以一种拙劣的方式依赖于业务逻辑中的不稳定代码

还有一个部分最难办。 复制/粘贴文字等操作可能会在多个地方被调用。 例如用户可以点击工具栏上小小的 “复制” 按钮, 或者通过上下文菜单复制一些内容, 又或者直接使用键盘上的 Ctrl+C 。

我们的程序最初只有工具栏, 因此可以使用按钮子类来实现各种不同操作。 换句话来说, ​ 复制按钮Copy­Button子类包含复制文字的代码是可行的。 在实现了上下文菜单、 快捷方式和其他功能后, 你要么需要将操作代码复制进许多个类中, 要么需要让菜单依赖于按钮, 而后者是更糟糕的选择。

解决方案 😃

优秀的软件设计通常会将关注点进行分离, 而这往往会导致软件的分层。 最常见的例子: 一层负责用户图像界面; 另一层负责业务逻辑。 GUI 层负责在屏幕上渲染美观的图形, 捕获所有输入并显示用户和程序工作的结果。 当需要完成一些重要内容时 (比如计算月球轨道或撰写年度报告), GUI 层则会将工作委派给业务逻辑底层。

这在代码中看上去就像这样: 一个 GUI 对象传递一些参数来调用一个业务逻辑对象。 这个过程通常被描述为一个对象发送请求给另一个对象。

GUI 层可以直接访问业务逻辑层。

命令模式建议 GUI 对象不直接提交这些请求。 你应该将请求的所有细节 (例如调用的对象、 方法名称和参数列表) 抽取出来组成命令类, 该类中仅包含一个用于触发请求的方法。

命令对象负责连接不同的 GUI 和业务逻辑对象。 此后, GUI 对象无需了解业务逻辑对象是否获得了请求, 也无需了解其对请求进行处理的方式。 GUI 对象触发命令即可, 命令对象会自行处理所有细节工作。

GUI 层可以直接访问业务逻辑层。

下一步是让所有命令实现相同的接口。 该接口通常只有一个没有任何参数的执行方法, 让你能在不和具体命令类耦合的情况下使用同一请求发送者执行不同命令。 此外还有额外的好处, 现在你能在运行时切换连接至发送者的命令对象, 以此改变发送者的行为。

你可能会注意到遗漏的一块拼图——请求的参数。 GUI 对象可以给业务层对象提供一些参数。 但执行命令方法没有任何参数, 所以我们如何将请求的详情发送给接收者呢? 答案是: 使用数据对命令进行预先配置, 或者让其能够自行获取数据。

GUI 层可以直接访问业务逻辑层。

让我们回到文本编辑器。 应用命令模式后, 我们不再需要任何按钮子类来实现点击行为。 我们只需在 按钮Button基类中添加一个成员变量来存储对于命令对象的引用, 并在点击后执行该命令即可。

你需要为每个可能的操作实现一系列命令类, 并且根据按钮所需行为将命令和按钮连接起来。

模式结构

GUI 层可以直接访问业务逻辑层。

  • 发送者 (Sender)——亦称 “触发者 (Invoker)”——类负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用。 发送者触发命令, 而不向接收者直接发送请求。 注意, 发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令。
  • 命令 (Command) 接口通常仅声明一个执行命令的方法。
  • 具体命令 (Concrete Commands) 会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码, 这些类可以进行合并。接收对象执行方法所需的参数可以声明为具体命令的成员变量。 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化。
  • 接收者 (Receiver) 类包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作。
  • 客户端 (Client) 会创建并配置具体命令对象。 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。 此后, 生成的命令就可以与一个或多个发送者相关联了。

示例代码


#include <string>
class Command
{
public:
	virtual ~Command(){}
	virtual void Execute() const = 0;
};

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 {
private:
	Receiver *receiver_;

	std::string a_;
	std::string b_;
public:
	ComplexCommand(Receiver *receiver, std::string a, std::string b) : receiver_(receiver), a_(a), b_(b) {
	}

	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_);
	}
};

class Invoker
{
private:
	Command *on_start_;
	Command *on_finish_;
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;
}

适合应用场景

  • 如果你需要通过操作来参数化对象, 可使用命令模式。
  • 如果你想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式。
  • 如果你想要实现操作回滚功能, 可使用命令模式。

posted on 2021-05-24 16:06  Ultraman_X  阅读(82)  评论(0编辑  收藏  举报

导航