命令模式将命令或者请求封装成一个对象,使用时,我们将命令传递给命令的调用者,命令的调用者对于命令如何执行一无所知,只需调用命令的方法来完成命令。命令锁操作的对象(命令接收者)与命令调用者完全分开。其结构图如下:
命令接口定义提供哪些命令,在具体的命令子类中,实现具体的命令操作,具体子类持有命令作用对象,也就是命令接收者的引用。用户类将命令接收者作为构造函数的参数传递给命令对象,创建命令对象这话将命令传递给命令的调用者。
例如,我们有一个按钮类Button,按钮可以支持多种不同的命令,如OnCommand定义打开命令,NextCommand定义下一集命令,MuteCommand定义静音命令,这些命令有调用者Button调用。而命令操作的对象可以是TV或者MP3。这时候可以使用命令模式来解耦TV、MP3和Button之间的关系。这个例子的UML图如下:
首先定义命令接收者(命令操作的对象)设备类Device及其子类,Device类定义设备支持的操作,供命令类调用(而不是供Button调用):
/**
* @author Brandon B. Lin
*
*/
public interface Device {
public abstract void on();
public abstract void next();
public abstract void mute();
}
/**
* @author Brandon B. Lin
*
*/
public class TV implements Device {
@Override
public void on() {
System.out.println("TV On");
}
@Override
public void next() {
System.out.println("TV Next");
}
@Override
public void mute() {
System.out.println("Mute TV");
}
}
/**
* @author Brandon B. Lin
*
*/
public class MP3 implements Device {
@Override
public void on() {
System.out.println("MP3 On");
}
@Override
public void next() {
System.out.println("MP3 Next");
}
@Override
public void mute() {
System.out.println("Mute MP3");
}
}
/**
* @author Brandon B. Lin
*
*/
public class OnCommand implements Command {
private Device deviceToTurnOn;
public OnCommand(Device deviceToTurnOn) {
this.deviceToTurnOn = deviceToTurnOn;
}
@Override
public void execute() {
deviceToTurnOn.on();
}
}
/**
* @author Brandon B. Lin
*
*/
public class NextCommand implements Command {
private Device deviceToNext;
public NextCommand(Device deviceToNext) {
this.deviceToNext = deviceToNext;
}
@Override
public void execute() {
deviceToNext.next();
}
}
/**
* @author Brandon B. Lin
*
*/
public class MuteAllCommand implements Command {
private List devicesToMute;
public MuteAllCommand(List devicesToMute) {
this.devicesToMute = devicesToMute;
}
@Override
public void execute() {
for (Device device : devicesToMute) {
device.mute();
}
}
}
/**
* @author Brandon B. Lin
*
*/
public class Button {
private Command commandWhenClicked;
public Button(String displayName, Command commandWhenClicked) {
this.commandWhenClicked = commandWhenClicked;
}
public void click() {
commandWhenClicked.execute();
}
}
/**
* @author Brandon B. Lin
*
*/
public class CommandTest {
public static void main(String[] args) {
TV tv = new TV();
OnCommand onTVCommand = new OnCommand(tv);
new Button("OnTV", onTVCommand).click();
MP3 mp3 = new MP3();
NextCommand nextMP3Command = new NextCommand(mp3);
new Button("NextMP3", nextMP3Command).click();
List deviceToMute = new ArrayList<>();
deviceToMute.add(tv);
deviceToMute.add(mp3);
MuteAllCommand muteAllCommand = new MuteAllCommand(deviceToMute);
new Button("MuteAllButton", muteAllCommand).click();
}
}
命令模式是回调函数Callback的面向对象版本,它可以实现撤销(undo)操作。
Java中,线程是命令模式的典型例子。Runnable接口相当于命令模式中的Command,而Thread相当于命令的调用者,只不过这个调用动作是Java自动完成,不需要我们手动编写代码,而在上面的例子中,我们需要调用Button类的click方法。线程不关心Runnable如何执行,操作哪些对象,它只简单执行Runnable中定义的run方法,然后把注意力放在线程本身的管理和控制上。这样一来,Thread和Runnable具体操作的对象实现解耦,很容易定义一个不同的命令(Runnable)而无需更改Thread类。
另外,javax.swing.Action也是命令模式的实现。