【Head First 模式设计】第6章 命令模式


the Command Pattern
Encapsulating Invocation


通过封装方法调用 (method invocation),我们可以使计算的各个部分具体化,从而使调用该计算的对象无需担心如何做,它只需使用我们的具体化的方法即可完成工作。
我们还可以使用这些封装的方法调用来完成一些聪明的事情,例如,将它们保存下来以进行日志记录,或重复使用它们以在我们的代码中实现撤消 (undo)。


设计一个家电自动化遥控器的API

遥控器具有 7 个可编程插槽 (每个插槽可分配给不同的家用设备) 以及每个插槽的相应开/关按钮。遥控器还具有一个整体的撤消按钮。
已提供由多家供应商开发的一组类,用于控制家庭自动化设备,如灯、风扇、热水器,音频设备和其他类似的可控制设备。
要求:创建一组用于对遥控器进行编程的API,以便可以分配每个插槽来控制一个设备或一组设备。注意,要能够控制当前设备以及供应商可能提供的任何未来的设备。
遥控器

供应商的类:

ApplianceControl on() off() Light on() off() Hottub circulate() jetsOn() jetsOff() setTemperaturet() Stereo on() off() setCd() setDvd() setRadio() setVolume() CeilingFan high() medium() low() off() getSpeed() GarageDoor up() down() stop() lightOn() lightOff() OutdoorLight on() off() GardenLight setDuskTime() setDawnTime() manualOn() manualOff()

目前,有一个带有开/关按钮的简单遥控器,但是有一组各种各样的供应商类。

遥控器应该知道如何解释按钮按下的动作,并发出请求,但是遥控器不需知道太多家电自动化的细节或如何打开热水器。

命令模式可以将操作的请求者与实际执行该操作的对象解耦。这个例子中的请求者是遥控器,执行该操作的对象是供应商的某个类的一个实例。

可以通过在设计中引入“命令对象”来实现解耦。命令对象封装了对特定对象 (如客厅电灯对象) 执行某项操作 (如打开电灯) 的请求。因此,如果为每个按钮存储一个命令对象,那么当按下按钮时,要求命令对象做一些工作。遥控器不知道工作是什么,它只有一个命令对象,该对象知道如何与正确的对象沟通,完成工作。

命令模式简介

对象村餐厅是怎么工作的:

  1. 顾客订单交给服务员——createOrder()
  2. 服务员拿了订单——takeOrder(),将其放到订单柜台,并喊一声“订单来了”——orderUp()
  3. 快餐厨师根据订单准备餐点——makeBurger(), makeShake()

对象村餐厅的角色和职责:

  • 一张订单封装了准备餐点的请求。可以将订单视为一个对象,该对象作为准备饭菜的请求。它有一个接口,仅由 orderUp() 方法组成,这个方法封装了准备餐点所需的操作。
  • 服务员的工作是从顾客那里获取订单,并调用订单上的 orderUp() 方法。服务员的 takeOrder() 方法可以根据不同订单传入不同的参数。
  • 快餐厨师具有准备餐点所需的知识。一旦服务员调用了 orderUp() 方法,厨师接手实现制作餐点所需的所有方法。服务员和厨师完全解耦。

从餐厅到命令模式

餐厅的工作模式反映出命令模式,所有角色都一样,只有名字改变了。

  1. 客户 (Client) 负责创建命令对象 createCommandObject(),命令对象提供一个方法 execute(),这个方法封装了接收者 (Receiver) 中的一组动作。
  2. Client 利用 setCommand() 将命令对象存储在调用者 (Invoker) 中。
  3. 在某个时间点,Invoker 调用命令对象的 execute() 方法,这会导致 Receiver 的一些动作被调用 action1(), action2()

注意,一旦命令被加载到调用者中,该命令可以被使用并丢弃,或者保留下来并被使用多次。

餐厅的对象和方法对应到命令模式的相应名称:

餐厅命令模式
顾客Client
订单Command
服务员Invoker
快餐厨师Receiver
orderUp()execute()
takeOrder()setCommand()

第一个命令对象

虽然目前还未弄清楚如何设计遥控器API,自下而上构建一些事物可能会有帮助。

实现命令接口

首先,所有命令实现相同的接口,这个接口由一个方法组成,一般将这个方法命名为 execute()。

class Command {
public:
	virtual void execute() const = 0;
};

实现一个打开电灯的命令

根据供应商所提供的类,Light 类有两个方法:on() 和 off()。

// 这是一个命令,所以需要实现 Command 接口
class LightOnCommand : public Command {
private:
	const Light* light;

public:
	// 向构造函数传递此命令将要控制的特定电灯,并将其存储在light实例变量中。
	// 当execute被调用时,这个Light对象将成为请求的接收者。
	explicit LightOnCommand(const Light* light) {
		this->light = light;
	}
	void execute() const {
		light->on();
	}
};

简单模拟 Light 类的实现:

class Light {
public:
	Light() { }
	void on() const {
		std::cout << "Light is on" << std::endl;
	}
	void off() const {
		std::cout << "Light is off" << std::endl;
	}
};

使用命令对象

简化一下:假设有一个遥控器,它只有一个按钮和对应的插槽,可以控制一个设备。

class SimpleRemoteControl {
private:
	const Command* slot;	// 一个卡槽持有命令,这个命令控制一个设备

public:
	SimpleRemoteControl() : slot(0) { }
	void setCommand(const Command* command) {	// 设置插槽控制的命令
		slot = command;
	}
	void buttonWasPressed() const {
		slot->execute();
	}
};

创建一个简单测试使用遥控器

// 这是命令模式的Client
int main() {
	std::unique_ptr<SimpleRemoteControl> remote(new SimpleRemoteControl());	// 遥控器是Invoker。可以向它传递一个命令用来发出请求
	std::unique_ptr<Light> light(new Light());	// Light对象是请求的Receiver
	std::unique_ptr<LightOnCommand> lightOn(new LightOnCommand(light.get()));	// 创建一个命令并将Receiver传递给它

	remote->setCommand(lightOn.get());	// 将命令传递给Invoker
	remote->buttonWasPressed();			// 模拟按下按钮

	return 0;
}

运行结果:
命令模式运行结果


命令模式的定义与类图

命令模式 (Command Pattern) 的正式定义:

命令模式将一个请求封装为一个对象,从而可以使用不同的请求、队列或日志请求来参数化其他对象,并支持可撤销的操作。

下图是一个被封装的请求:
一个封装的请求
一个 Invoker (比如遥控器插槽) 可以使用不同的请求 (比如LightOnCommand) 作为参数。

命令模式类图
命令模式类图


实现遥控器

//
// This is the invoker
//
class RemoteControl {
private:
	static const int slots = 7;
	Command* onCommands[slots];	// 此时要处理7个On和Off命令,这些命名存储在对应的数组中
	Command* offCommands[slots];
	Command* noCommand;

public:
	RemoteControl() {
		noCommand = new NoCommand();
		for (int i = 0; i < slots; i++) {
			onCommands[i] = noCommand;
			offCommands[i] = noCommand;
		}
	}
	~RemoteControl() {
		delete noCommand;
	}
	// setCommand有3个参数,分别表示卡槽位置,该卡槽存储的On命令和Off命令。将这些命令放入相应数组中供稍后使用
	void setCommand(int slot, Command* onCommand, Command* offCommand) {
		onCommands[slot] = onCommand;
		offCommands[slot] = offCommand;
	}
	// 当按下On按钮,硬件就会调用onButtonWasPushed方法
	void onButtonWasPushed(int slot) const {
		onCommands[slot]->execute();
	}
	// 当按下Off按钮,硬件就会调用offButtonWasPushed方法
	void offButtonWasPushed(int slot) const {
		offCommands[slot]->execute();
	}
	// 覆盖toString(),打印每个卡槽及其对应的命令
	std::string toString() const {
		std::stringstream stringBuff;
		stringBuff << "\n------ Remote Control -------\n" << std::endl;
		for (int i = 0; i < slots; i++) {
			stringBuff  << "[slot " << i << "] "
						<< typeid(*onCommands[i]).name()	// typeid().name()可以返回变量、函数、类的数据类型名
						<< "    "
						<< typeid(*offCommands[i]).name()
						<< std::endl;
		}
		return stringBuff.str();
	}
};

实现命令

比如,如何为音响 (Stereo) 编写开/关命令?

class StereoOnWithCDCommand : public Command {
private:
	const Stereo* stereo;
 
public:
	explicit StereoOnWithCDCommand(const Stereo* stereo) {
		this->stereo = stereo;
	}
	void execute() const {	// 调用 Stereo 的 3 个方法
		stereo->on();			// 打开音响
		stereo->setCD();		// 把模式设置成播放CD
		stereo->setVolume(11);	// 把音量设置为11
	}
};

测试遥控器

int main() {
	std::unique_ptr<RemoteControl> remoteControl(new RemoteControl());
	
	// 在正确的位置创建所有设备
	std::unique_ptr<Light> livingRoomLight(new Light("Living Room"));
	std::unique_ptr<Light> kitchenLight(new Light("Kitchen"));
	std::unique_ptr<CeilingFan> ceilingFan(new CeilingFan("Living Room"));
	std::unique_ptr<GarageDoor> garageDoor(new GarageDoor("Garage"));
	std::unique_ptr<Stereo> stereo(new Stereo("Living Room"));
	
	// 创建所有电灯命令对象
	std::unique_ptr<LightOnCommand> livingRoomLightOn(new LightOnCommand(livingRoomLight.get()));
	std::unique_ptr<LightOffCommand> livingRoomLightOff(new LightOffCommand(livingRoomLight.get()));
	std::unique_ptr<LightOnCommand> kitchenLightOn(new LightOnCommand(kitchenLight.get()));
	std::unique_ptr<LightOffCommand> kitchenLightOff(new LightOffCommand(kitchenLight.get()));
	
	// 为吊扇创建开/关命令
	std::unique_ptr<CeilingFanOnCommand> ceilingFanOn(new CeilingFanOnCommand(ceilingFan.get()));
	std::unique_ptr<CeilingFanOffCommand> ceilingFanOff(new CeilingFanOffCommand(ceilingFan.get()));
	
	// 为车库创建上/下命令
	std::unique_ptr<GarageDoorUpCommand> garageDoorUp(new GarageDoorUpCommand(garageDoor.get()));
	std::unique_ptr<GarageDoorDownCommand> garageDoorDown(new GarageDoorDownCommand(garageDoor.get()));
	
	// 创建音响开/关命令
	std::unique_ptr<StereoOnWithCDCommand> stereoOnWithCD(new StereoOnWithCDCommand(stereo.get()));
	std::unique_ptr<StereoOffCommand> stereoOff(new StereoOffCommand(stereo.get()));

	// 将命令加载到遥控器插槽中
	remoteControl->setCommand(0, livingRoomLightOn.get(), livingRoomLightOff.get());
	remoteControl->setCommand(1, kitchenLightOn.get(), kitchenLightOff.get());
	remoteControl->setCommand(2, ceilingFanOn.get(), ceilingFanOff.get());
	remoteControl->setCommand(3, stereoOnWithCD.get(), stereoOff.get());
	remoteControl->setCommand(4, garageDoorUp.get(), garageDoorDown.get());

	std::cout << remoteControl->toString() << std::endl;

	// 逐步按下插槽的开/关按钮
	remoteControl->onButtonWasPushed(0);
	remoteControl->offButtonWasPushed(0);
	remoteControl->onButtonWasPushed(1);
	remoteControl->offButtonWasPushed(1);
	remoteControl->onButtonWasPushed(2);
	remoteControl->offButtonWasPushed(2);
	remoteControl->onButtonWasPushed(3);
	remoteControl->offButtonWasPushed(3);
	remoteControl->onButtonWasPushed(4);
	remoteControl->offButtonWasPushed(4);

	return 0;
}

简单模拟一下 Light 类的实现:

class Light {
private:
	std::string location;

public:
	explicit Light(std::string location) {
		this->location = location;
	}
	void on() const {
		std::cout << location.c_str() << " light is on" << std::endl;
	}
	void off() const {
		std::cout << location.c_str() << " light is off" << std::endl;
	}
};

测试结果:

------ Remote Control -------

[slot 0] class LightOnCommand    class LightOffCommand
[slot 1] class LightOnCommand    class LightOffCommand
[slot 2] class CeilingFanOnCommand    class CeilingFanOffCommand
[slot 3] class StereoOnWithCDCommand    class StereoOffCommand
[slot 4] class GarageDoorUpCommand    class GarageDoorDownCommand
[slot 5] class NoCommand    class NoCommand
[slot 6] class NoCommand    class NoCommand

Living Room light is on
Living Room light is off
Kitchen light is on
Kitchen light is off
Living Room ceiling fan is on high
Living Room ceiling fan is off
Living Room stereo is on
Living Room stereo is set for CD input
Living Room Stereo volume set to 11
Living Room stereo is off
Garage Door is Up
Garage Door is Down

空对象

class NoCommand : public Command{
public:
	void execute() const {};
};

NoCommand 对象是空对象 (null object) 的一个例子。如果没有有意义的对象要返回,但又不想在客户端处理 null,那么空对象就很有用。
空对象可以与许多设计模式结合使用,有时甚至会看到空对象被列为设计模式。

写文档

用于家电自动化的遥控器API设计

我们的主要设计目标是使遥控器代码尽可能简单,以便在新的供应商类时出现时无需进行更改。为此,我们采用了命令模式,从逻辑上将 RemoteControl 类与 Vendor 类解耦。我们相信,这将减少遥控器的生产成本,并大大降低你的日常维护成本。

以下类图概述了我们的设计:
遥控器API类图


撤销 (undo)

需要添加功能,支持遥控器上的撤消按钮。
它的工作方式是这样的:假设客厅电灯是关闭的,然后按遥控器上的 on 按钮,灯亮了。现在,如果按撤消按钮,那么上一个操作将被撤消——在这种情况下,电灯将关闭。

实现撤销

当命令支持撤消时,它们具有一个与 execute() 方法相反的 undo() 方法。首先需要在 Command 接口中添加 undo() 方法:

class Command {
public:
	virtual void execute() const = 0;
	virtual void undo() const = 0;
};

以 LightOnCommand 为例:如果调用了 LightOnCommand 的 execute() 方法,则最终调用的是 on() 方法。undo() 需要通过调用 off() 方法来做相反的事情。

class LightOnCommand : public Command {
private:
	Light* light;
 
public:
	explicit LightOnCommand(Light* light) {
		this->light = light;
	}
	void execute() const {
		light->on();
	}
	void undo() const {
		light->off();
	}
};

要添加对撤消按钮的支持,需要对 Remote Control 类进行一些小的更改。执行以下操作:添加一个新的实例变量以跟踪上一次调用的命令;然后,每当按下 undo 按钮时,就会检索该命令并调用其 undo() 方法。

class RemoteControlWithUndo {
private:
	static const int slots = 7;
	Command* onCommands[slots];
	Command* offCommands[slots];
	Command* noCommand;
	mutable Command* undoCommand;	// 为撤消按钮存储执行的最后命令

public:
	RemoteControlWithUndo() {
		noCommand = new NoCommand();
		for (int i = 0; i < slots; i++) {
			onCommands[i] = noCommand;
			offCommands[i] = noCommand;
		}
		undoCommand = noCommand;	// 在其他任何按钮之前按undo根本不会执行任何操作
	}
	~RemoteControlWithUndo() {
		delete noCommand;
	}
	void setCommand(int slot, Command* onCommand, Command* offCommand) {
		onCommands[slot] = onCommand;
		offCommands[slot] = offCommand;
	}
	void onButtonWasPushed(int slot) const {
		onCommands[slot]->execute();
		undoCommand = onCommands[slot];	// 当按下一个按钮时,首先执行命令;然后将命令引用保存在undoCommand实例变量中
	}
	void offButtonWasPushed(int slot) const {
		offCommands[slot]->execute();
		undoCommand = offCommands[slot];
	}
	void undoButtonWasPushed() const {
		undoCommand->undo();	// 当按下撤消按钮时,调用存储在undoCommand中的命令的undo()方法。这将逆转最后执行的命令的操作
	}
	std::string toString() const {
		std::stringstream stringBuff;
		stringBuff << std::endl << "------ Remote Control -------" << std::endl;
		for (int i = 0; i < slots; i++) {
			stringBuff  << "[slot " << i << "] "
						<< typeid(*onCommands[i]).name()
						<< "    "
						<< typeid(*offCommands[i]).name()
						<< std::endl;
		}
		stringBuff << "[undo] " << typeid(*undoCommand).name() << std::endl;
		return stringBuff.str();
	}
};

测试程序:

int main() {
	std::unique_ptr<RemoteControlWithUndo> remoteControl(new RemoteControlWithUndo());

	std::unique_ptr<Light> livingRoomLight(new Light("Living Room"));

	std::unique_ptr<LightOnCommand> livingRoomLightOn(new LightOnCommand(livingRoomLight.get()));
	std::unique_ptr<LightOffCommand> livingRoomLightOff(new LightOffCommand(livingRoomLight.get()));

	remoteControl->setCommand(0, livingRoomLightOn.get(), livingRoomLightOff.get());

	remoteControl->onButtonWasPushed(0);	// 打开电灯
	remoteControl->offButtonWasPushed(0);	// 关闭电灯
	std::cout << remoteControl->toString() << std::endl;
	remoteControl->undoButtonWasPushed();	// 撤销
	remoteControl->offButtonWasPushed(0);
	remoteControl->onButtonWasPushed(0);
	std::cout << remoteControl->toString() << std::endl;
	remoteControl->undoButtonWasPushed();

	return 0;
}

运行结果:

Living Room light is on
Living Room light is off

------ Remote Control -------
[slot 0] class LightOnCommand    class LightOffCommand
[slot 1] class NoCommand    class NoCommand
[slot 2] class NoCommand    class NoCommand
[slot 3] class NoCommand    class NoCommand
[slot 4] class NoCommand    class NoCommand
[slot 5] class NoCommand    class NoCommand
[slot 6] class NoCommand    class NoCommand
[undo] class LightOffCommand

Living Room light is on
Living Room light is off
Living Room light is on

------ Remote Control -------
[slot 0] class LightOnCommand    class LightOffCommand
[slot 1] class NoCommand    class NoCommand
[slot 2] class NoCommand    class NoCommand
[slot 3] class NoCommand    class NoCommand
[slot 4] class NoCommand    class NoCommand
[slot 5] class NoCommand    class NoCommand
[slot 6] class NoCommand    class NoCommand
[undo] class LightOnCommand

Living Room light is off

使用状态实现撤销

通常需要管理一些状态来实现撤销。以供应商类中的 CeilingFan 为例。吊扇具有 off 方法,还允许设置多种转速。

CeilingFan 的源代码:

class CeilingFan {
private:
	std::string location;
	mutable int speed;

public:
	static const int HIGH = 3;
	static const int MEDIUM = 2;
	static const int LOW = 1;
	static const int OFF = 0;

	explicit CeilingFan(std::string location) {
		this->location = location;
		speed = OFF;
	}
	void high() const {
		speed = HIGH;
		// code to set fan to high
		std::cout << location.c_str() << " ceiling fan is on high" << std::endl;
	} 
 	void medium() const {
		speed = MEDIUM;
		// code to set fan to medium
		std::cout << location.c_str() << " ceiling fan is on medium" << std::endl;
	}
 	void low() const {
		speed = LOW;
		// code to set fan to low
		std::cout << location.c_str() << " ceiling fan is on low" << std::endl;
	}
 	void off() const {
		speed = OFF;
		// code to turn fan off
		std::cout << location.c_str() << " ceiling fan is off" << std::endl;
	}
 	int getSpeed() const {
		return speed;
	}
};

将 Undo 添加到吊扇命令。以 CeilingFanHighCommand 为例:

class CeilingFanHighCommand : public Command {
private:
	const CeilingFan* ceilingFan;
	mutable int prevSpeed;
  
public:
	explicit CeilingFanHighCommand(const CeilingFan* ceilingFan) {
		this->ceilingFan = ceilingFan;
		prevSpeed = ceilingFan->getSpeed();
	}
 	void execute() const {
		prevSpeed = ceilingFan->getSpeed();
		ceilingFan->high();
	}
 	void undo() const {
		if (prevSpeed == CeilingFan::HIGH) {
			ceilingFan->high();
		} else if (prevSpeed == CeilingFan::MEDIUM) {
			ceilingFan->medium();
		} else if (prevSpeed == CeilingFan::LOW) {
			ceilingFan->low();
		} else if (prevSpeed == CeilingFan::OFF) {
			ceilingFan->off();
		}
	}
};

测试吊扇:

int main() {
	std::unique_ptr<RemoteControlWithUndo> remoteControl(new RemoteControlWithUndo());

	std::unique_ptr<CeilingFan> ceilingFan(new CeilingFan("Living Room"));

	std::unique_ptr<CeilingFanMediumCommand> ceilingFanMedium(new CeilingFanMediumCommand(ceilingFan.get()));
	std::unique_ptr<CeilingFanHighCommand> ceilingFanHigh(new CeilingFanHighCommand(ceilingFan.get()));
	std::unique_ptr<CeilingFanOffCommand> ceilingFanOff(new CeilingFanOffCommand(ceilingFan.get()));

	remoteControl->setCommand(0, ceilingFanMedium.get(), ceilingFanOff.get());
	remoteControl->setCommand(1, ceilingFanHigh.get(), ceilingFanOff.get());

	remoteControl->onButtonWasPushed(0);	// 中速开吊扇
	remoteControl->offButtonWasPushed(0);	// 关闭吊扇
	std::cout << remoteControl->toString() << std::endl;
	remoteControl->undoButtonWasPushed();	// 撤销,回到中速

	remoteControl->onButtonWasPushed(1);	// 高速开吊扇
	std::cout << remoteControl->toString() << std::endl;
	remoteControl->undoButtonWasPushed();	// 撤销,回到中速

	return 0;
}

运行结果:

Living Room ceiling fan is on medium
Living Room ceiling fan is off

------ Remote Control -------
[slot 0] class CeilingFanMediumCommand    class CeilingFanOffCommand
[slot 1] class CeilingFanHighCommand    class CeilingFanOffCommand
[slot 2] class NoCommand    class NoCommand
[slot 3] class NoCommand    class NoCommand
[slot 4] class NoCommand    class NoCommand
[slot 5] class NoCommand    class NoCommand
[slot 6] class NoCommand    class NoCommand
[undo] class CeilingFanOffCommand

Living Room ceiling fan is on medium
Living Room ceiling fan is on high

------ Remote Control -------
[slot 0] class CeilingFanMediumCommand    class CeilingFanOffCommand
[slot 1] class CeilingFanHighCommand    class CeilingFanOffCommand
[slot 2] class NoCommand    class NoCommand
[slot 3] class NoCommand    class NoCommand
[slot 4] class NoCommand    class NoCommand
[slot 5] class NoCommand    class NoCommand
[slot 6] class NoCommand    class NoCommand
[undo] class CeilingFanHighCommand

Living Room ceiling fan is on medium

宏命令

要求:按下一个按钮,就可以弄暗灯光、打开音响和电视、让热水器开始加热等。

可以创建一个新的命令,这个命令可以执行多个其他命令。

class MacroCommand : public Command {
private:
	std::vector<Command*> commands;
public: 
	MacroCommand(std::vector<Command*> commands) {
		this->commands = commands;	// 使用命令数组存储一些命令
	}
	void execute() const {
		for (int i = 0; i < commands.size(); ++i) {
			commands[i]->execute();	// 当宏命令通过遥控器执行时,一次性执行数组中的命令
		}
	}
	void undo() const {
		for (int i = 0; i < commands.size(); ++i) {
			commands[i]->undo();
		}
	}
};

使用宏命令

int main() {
	std::unique_ptr<RemoteControl> remoteControl(new RemoteControl());
	
	// 创建设备,电灯、音响
	std::unique_ptr<Light> livingRoomLight(new Light("Living Room"));
	std::unique_ptr<Stereo> stereo(new Stereo("Living Room"));
	
	// 创建命令控制设备
	std::unique_ptr<LightOnCommand> livingRoomLightOn(new LightOnCommand(livingRoomLight.get()));
	std::unique_ptr<LightOffCommand> livingRoomLightOff(new LightOffCommand(livingRoomLight.get()));
	std::unique_ptr<StereoOnWithCDCommand> stereoOnWithCD(new StereoOnWithCDCommand(stereo.get()));
	std::unique_ptr<StereoOffCommand> stereoOff(new StereoOffCommand(stereo.get()));
	
	// 创建命令数组
	std::vector<Command*> partyOn = { livingRoomLightOn.get() , stereoOnWithCD.get() };
	std::vector<Command*> partyOff = { livingRoomLightOff.get(), stereoOff.get() };
	
	// 创建两个对应的宏命令加载命令
	std::unique_ptr<MacroCommand> partyOnMacro(new MacroCommand(partyOn));
	std::unique_ptr<MacroCommand> partyOffMacro(new MacroCommand(partyOff));
	
	// 将宏命令指定给一个按钮
	remoteControl->setCommand(0, partyOnMacro.get(), partyOffMacro.get());

	std::cout << remoteControl->toString() << std::endl;
	
	// 按下按钮
	remoteControl->onButtonWasPushed(0);
	remoteControl->offButtonWasPushed(0);

	return 0;
}

运行结果:

------ Remote Control -------

[slot 0] class MacroCommand    class MacroCommand
[slot 1] class NoCommand    class NoCommand
[slot 2] class NoCommand    class NoCommand
[slot 3] class NoCommand    class NoCommand
[slot 4] class NoCommand    class NoCommand
[slot 5] class NoCommand    class NoCommand
[slot 6] class NoCommand    class NoCommand

Living Room light is on
Living Room stereo is on
Living Room stereo is set for CD input
Living Room Stereo volume set to 11
Living Room light is off
Living Room stereo is off

问:如何实现撤消操作的历史记录?换句话说,我希望能够多次按下“撤消”按钮。
答:可以使用栈保留一些先前的命令,而不仅仅是保留对最后执行的命令的引用。然后,每按一次 undo,你的调用程序就会将第一个项目从堆栈中弹出,并调用其 undo() 方法。


命令模式的更多用途:队列请求

命令提供了一种方法:打包运算块 (一个接收者和一组动作) 并可以像对象那样传递它。现在,在某些客户端应用程序创建命令对象很长时间之后,运算依然可以被调用。实际上,它甚至可以由其他线程调用。我们可以利用这种特性,将其应用于其他应用程序,例如调度程序、线程池和作业队列等。

想象一个作业队列:你在一端添加命令到队列,而在另一端放置一组线程。线程运行以下脚本:它们从队列中删除一条命令,调用其 execute() 方法,等待调用完成,然后丢弃该命令对象并检索一个新的对象。

注意,作业队列类与进行运算的对象完全解耦。线程可能在一分钟内正在进行财务计算,而下一分钟可能正在从网络中检索某些内容。作业队列对象不在意进行什么运算,它们只是检索命令并调用 execute()。同样,只要将对象放入实现命令模式的队列中,当线程可用时,将调用 execute() 方法

命令模式的更多用途:日志请求

某些应用程序的要求记录所有动作,并且能够在系统崩溃后通过重新调用这些动作来恢复。命令模式可以通过添加两个方法来支持这种情况:store() 和 load()。

Command execute() undo() store() load()

在Java中,可以使用对象序列化来实现这些方法,但是一般认为序列化用于对象的持久化。

这是如何工作的?在执行命令时,将它们的历史记录存储在磁盘上。发生系统崩溃时,重新加载命令对象,并按顺序批量调用它们的 execute() 方法。

有许多应用程序会在大型数据结构上调用动作,而这些动作无法在每次更改时快速保存。通过使用日志记录,可以保存自上一个检查点 (check point) 之后的所有操作,如果系统发生故障,将这些操作应用于检查点。
以一个电子表格应用程序为例:我们可能希望实现故障恢复的方式是,在电子表格上记录动作,而不是每次发生更改时都将电子表格的副本写入磁盘。
在更高级的应用程序中,可以将这些技术扩展应用到事务处理中的操作集合,以便完成所有操作,或者不执行任何操作。


要点

  • 命令模式将发出请求的对象和执行请求的对象解耦。
  • Command 对象位于解耦对象之间,封装一个接收者的一个动作 (或一组动作)。
  • 调用者通过调用 Command 对象的 execute() 方法来发出请求,该方法会调用接收者的一些动作。
  • 调用者可以接受命令作为参数,甚至可以在运行时动态地进行参数化。
  • 命令可以通过实现 undo 方法来支持撤消,该方法可以将对象恢复到上次调用 execute() 方法之前的状态。
  • 宏命令是命令的简单扩展,它允许调用多个命令。同样,宏命令可以支持 undo()。
  • 实际上,“智能”命令对象自己实现请求而不是委派给接收者并不罕见。
  • 命令也可以用于实现日志记录和事务处理系统。

【Head First 模式设计】目录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值