和之前的文章一样,让我们用一个设计问题来理解命令模式。假设您正在构建一个家庭自动化系统。有一个可编程遥控器,可用于打开和关闭家中的各种物品,如电灯、立体声音响、交流电等。它看起来像这样。
您可以使用简单的 if-else 语句来完成,例如
if (buttonPressed == button1)
lights.on()
但我们需要记住,打开某些设备(如立体声)包括许多步骤,如设置 cd、音量等。我们也可以重新分配一个按钮来做其他事情。通过使用简单的 if-else,我们编码的是实现而不是接口。还有紧耦合。
所以我们想要实现的是一种提供松耦合和远程控制的设计,不应该有关于特定设备的太多信息。命令模式可以帮助我们做到这一点。
定义:命令模式将一个请求封装成一个对象,从而让我们可以参数化具有不同请求、队列或日志请求的其他对象,并支持可撤销的操作。
这个定义起初有点令人困惑,但让我们逐步了解它。类似于我们上面的遥控器问题是客户端和立体声,灯等是接收器。在命令模式中,有一个 Command 对象,它通过将特定接收器上的一组动作绑定在一起来封装请求。它通过仅公开一个方法 execute() 来实现,该方法会导致在接收器上调用某些操作。
在我们的类比中,参数化具有不同请求的其他对象意味着用于打开灯的按钮稍后可用于打开立体声或打开车库门。
队列或日志请求,并支持可撤销的操作,这意味着 Command 的 Execute 操作可以存储状态以在 Command 本身中反转其效果。 Command 可能有一个添加的 unExecute 操作,可以逆转之前调用 execute 的效果。它还可能支持日志记录更改,以便在系统崩溃的情况下可以重新应用它们。
下面是上述远程控制示例的Java实现:
// A simple Java program to demonstrate
// implementation of Command Pattern using
// a remote control example.
// An interface for command
interface Command
{
public void execute();
}
// Light class and its corresponding command
// classes
class Light
{
public void on()
{
System.out.println("Light is on");
}
public void off()
{
System.out.println("Light is off");
}
}
class LightOnCommand implements Command
{
Light light;
// The constructor is passed the light it
// is going to control.
public LightOnCommand(Light light)
{
this.light = light;
}
public void execute()
{
light.on();
}
}
class LightOffCommand implements Command
{
Light light;
public LightOffCommand(Light light)
{
this.light = light;
}
public void execute()
{
light.off();
}
}
// Stereo and its command classes
class Stereo
{
public void on()
{
System.out.println("Stereo is on");
}
public void off()
{
System.out.println("Stereo is off");
}
public void setCD()
{
System.out.println("Stereo is set " +
"for CD input");
}
public void setDVD()
{
System.out.println("Stereo is set"+
" for DVD input");
}
public void setRadio()
{
System.out.println("Stereo is set" +
" for Radio");
}
public void setVolume(int volume)
{
// code to set the volume
System.out.println("Stereo volume set"
+ " to " + volume);
}
}
class StereoOffCommand implements Command
{
Stereo stereo;
public StereoOffCommand(Stereo stereo)
{
this.stereo = stereo;
}
public void execute()
{
stereo.off();
}
}
class StereoOnWithCDCommand implements Command
{
Stereo stereo;
public StereoOnWithCDCommand(Stereo stereo)
{
this.stereo = stereo;
}
public void execute()
{
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
// A Simple remote control with one button
class SimpleRemoteControl
{
Command slot; // only one button
public SimpleRemoteControl()
{
}
public void setCommand(Command command)
{
// set the command the remote will
// execute
slot = command;
}
public void buttonWasPressed()
{
slot.execute();
}
}
// Driver class
class RemoteControlTest
{
public static void main(String[] args)
{
SimpleRemoteControl remote =
new SimpleRemoteControl();
Light light = new Light();
Stereo stereo = new Stereo();
// we can change command dynamically
remote.setCommand(new
LightOnCommand(light));
remote.buttonWasPressed();
remote.setCommand(new
StereoOnWithCDCommand(stereo));
remote.buttonWasPressed();
remote.setCommand(new
StereoOffCommand(stereo));
remote.buttonWasPressed();
}
}
输出:
Light is on
Stereo is on
Stereo is set for CD input
Stereo volume set to 11
Stereo is off
请注意,遥控器对打开立体声系统一无所知。该信息包含在单独的命令对象中。这减少了它们之间的耦合。
好处:
- 使我们的代码可扩展,因为我们可以在不更改现有代码的情况下添加新命令。
- 减少命令的调用者和接收者的耦合。
缺点:
- 增加每个单独命令的类数
参考:
- Head First 设计模式(书籍)
- https://github.com/bethrobson/Head-First-Design-Patterns/tree/master/src/headfirst/designpatterns/command