设计模式:第六篇--命令模式
命令模式模型
命令模式:将“请求”封装成对象,以便使用不同的请求,队列或日志来参数化其他对象,支持撤销命令的操作。实现“动作的请求者”从“动作的执行者”中解耦。
命令模式理解:很简单的一个思想,抽象了命令,同时提供了操作这个命令对象的角色Invoker。命令请求于命令执行,两个东西是耦合到一起的。抽象了命令,和提供了Invoker之后。Client可以向Invoker提出具体的命令请求。具体的命令是耦合具体的Receiver(具体的命令执行者)。由Invoker去封装命令并且发出“执行命令的请求”。这个过程,其实是将命令请求从 client-->Receiver 转移到 Client-->Invoker-->Receiver。抽象和封装有时候就是这样,多一个角色去协调,那么便可以拓展和管理被协调的两方面了。
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/26 10:22.
*
* 命令模式的Command:抽象命令对象
*/
public interface Command {
void execute();
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/26 10:24.
*
* 命令模式的Command:具体命令对象
*/
public class ConcreteCommand implements Command{
//接收命令对象
Receiver receiver;
public ConcreteCommand(Receiver light) {
this.receiver = light;
}
@Override
public void execute() {
this.receiver.action();
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/26 10:26.
*
* 命令模式的Invoker:调用者对象,调用者传递命令
*/
public class Invoker {
//插槽持有命令
Command command;
/**
* 设置命令
* @param command
*/
public void setCommand(Command command) {
this.command = command;
}
/**
* 将命令封装进按下按钮动作中
*/
public void buttonWasPressed() {
command.execute();
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/26 10:23.
*
* 命令模式的Receiver:接收者(具体动作执行者)
*/
public class Receiver {
/**
* 具体的命令要执行的动作
*/
void action(){
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/26 10:47.
*
* client:命令模式的客户,客户通过Invoker设置命令
*/
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker();
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
invoker.setCommand(command);
invoker.buttonWasPressed();
}
}
案例:家居遥控器
需求:装修了,换一套智能家居。需要遥控器来控制家里的一些,比如灯,音响之类的一些家具。
思考:原来我需要到开关上去控制开关,现在转移到遥控器上了,命令的请求从命令的执行者,转移到协调者(Invoker)去了。这不正符合命令模式吗。用命令模式设计一下。
- 第一步:首先是实际的执行者(Receiver)
public class Light {
private String description;
public Light(String description) {
this.description = description;
}
public void on() {
System.out.println(description + " on");
}
public void off() {
System.out.println(description + " off");
}
}
public class Stereo {
private String description;
public Stereo(String description) {
this.description = description;
}
public void on() {
System.out.println(description + "Stereo on");
}
public void off() {
System.out.println(description + "Stereo off");
}
public void setCD(){
System.out.println(description + "Stereo set CD");
}
public void setVolume(int volume){
System.out.println(description + "Stereo set volume is " + volume);
}
}
- 第二步:抽象命令接口,提供向Receiver请求执行具体命令的具体命令对象。
public interface Command {
void execute();
}
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
}
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
public class StereoOnWithCDComand implements Command {
private Stereo stereo;
public StereoOnWithCDComand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
public class StereoOffWithCDComand implements Command {
private Stereo stereo;
public StereoOffWithCDComand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.off();
}
}
public class NoCommand implements Command {
/**
* 空对象,什么都不做
*/
@Override
public void execute() {
}
}
- 第三步:提供遥控器向Receiver请求命令之间的协调者(Invoker)
public class RemoteControl {
/**
* 记录命令
*/
private Command[] onCommands;
private Command[] offCommands;
public RemoteControl() {
//设置7个命令插槽
onCommands = new Command[7];
offCommands = new Command[7];
NoCommand noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
/**
* 提供设置插槽命令的方法
* @param slot
* @param onCommand
* @param offCommand
*/
public void setCommand(int slot, Command onCommand, Command offCommand) {
this.onCommands[slot] = onCommand;
this.offCommands[slot] = offCommand;
}
/**
* 封装出来的具体命令动作
* @param slot
*/
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}
/**
* 封装出来的具体命令动作
* @param slot
*/
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
}
@Override
public String toString() {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < onCommands.length; i++) {
stringBuffer.append("[")
.append("slot ")
.append(i)
.append("]")
.append(onCommands[i].getClass().getName())
.append(" ")
.append(offCommands[i].getClass().getName())
.append("\n");
}
return stringBuffer.toString();
}
}
- 第四步:实现遥控器(client)
public class RemoteLoader {
public static void main(String[] args) {
//1、获取Invoker,协调者
RemoteControl remoteControl = new RemoteControl();
//2、设置Reveiver,实际命令执行者
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
Stereo livingRoomStereo = new Stereo("Living Room");
//设置命令,并绑定实际命令执行者
LightOffCommand livingRoomLightOff= new LightOffCommand(livingRoomLight);
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
StereoOnWithCDComand stereoOnWithCDComand = new StereoOnWithCDComand(livingRoomStereo);
StereoOffWithCDComand stereoOffWithCDComand = new StereoOffWithCDComand(livingRoomStereo);
//3、封装命令到Invoker协调者
remoteControl.setCommand(0,livingRoomLightOn,livingRoomLightOff);
remoteControl.setCommand(1,kitchenLightOn,kitchenLightOff);
remoteControl.setCommand(2,stereoOnWithCDComand,stereoOffWithCDComand);
System.out.println(remoteControl);
//4、Invoker,协调者发出执行命令的请求
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
}
}
总结:命令模式的核心在于抽象命令,并提供命令请求的协调者。怎加一个角色解耦另外两个角色。实现拓展。
撤销命令
需求:命令支持撤销
思考:本质上撤销就是执行历史命令,或者执行乡放的命令。
做法:命令接口提供与execute()方法相反的undo()方法,具体命令提供实现即可。
public interface Command {
void execute();
void undo();
}
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
记录撤销的命令
public class RemoteControlWithUndo {
/**
* 记录命令
*/
private Command[] onCommands;
private Command[] offCommands;
//记录要撤销的命令
private Command undoCommand;
public RemoteControlWithUndo() {
//设置7个命令插槽
onCommands = new Command[7];
offCommands = new Command[7];
NoCommand noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
/**
* 提供设置插槽命令的方法
*
* @param slot
* @param onCommand
* @param offCommand
*/
public void setCommand(int slot, Command onCommand, Command offCommand) {
this.onCommands[slot] = onCommand;
this.offCommands[slot] = offCommand;
}
/**
* 封装出来的具体命令动作
*
* @param slot
*/
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
/**
* 封装出来的具体命令动作
*
* @param slot
*/
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
/**
* 撤销方法
*/
public void undoButtonWasPushed() {
undoCommand.undo();
}
@Override
public String toString() {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < onCommands.length; i++) {
stringBuffer.append("[")
.append("slot ")
.append(i)
.append("]")
.append(onCommands[i].getClass().getName())
.append(" ")
.append(offCommands[i].getClass().getName())
.append("\n");
}
return stringBuffer.toString();
}
}
使用并测试
public class RemoteLoader {
public static void main(String[] args) {
//1、获取Invoker,协调者
//RemoteControl remoteControl = new RemoteControl();
//使用带撤销命令的协调者
RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
//2、设置Reveiver,实际命令执行者
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
Stereo livingRoomStereo = new Stereo("Living Room");
//设置命令,并绑定实际命令执行者
LightOffCommand livingRoomLightOff= new LightOffCommand(livingRoomLight);
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
StereoOnWithCDComand stereoOnWithCDComand = new StereoOnWithCDComand(livingRoomStereo);
StereoOffWithCDComand stereoOffWithCDComand = new StereoOffWithCDComand(livingRoomStereo);
//3、封装命令到Invoker协调者
remoteControl.setCommand(0,livingRoomLightOn,livingRoomLightOff);
remoteControl.setCommand(1,kitchenLightOn,kitchenLightOff);
remoteControl.setCommand(2,stereoOnWithCDComand,stereoOffWithCDComand);
System.out.println(remoteControl);
//4、Invoker,协调者发出执行命令的请求
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
//撤销命令
System.out.println("撤销命令:");
remoteControl.undoButtonWasPushed();
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
}
}
备注:如果要撤销一组命令,可以将记录命令的对象改为记录命令对象的堆栈,每次按下撤销只要调用栈顶对象的undo方法即可
宏命令
需求:现在遥控器要可以一键打开或关闭厨房、客厅的灯。
思考:拓展一个宏命令对象,组长其他命令即可。
public class MacroCommand implements Command {
Command[] commands;
public MacroCommand(Command[] commands) {
this.commands = commands;
}
@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}
@Override
public void undo() {
for (Command command : commands) {
command.undo();
}
}
}
public class RemoteLoader {
public static void main(String[] args) {
//1、获取Invoker,协调者
//RemoteControl remoteControl = new RemoteControl();
//使用带撤销命令的协调者
RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
//2、设置Reveiver,实际命令执行者
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
Stereo livingRoomStereo = new Stereo("Living Room");
//设置命令,并绑定实际命令执行者
LightOffCommand livingRoomLightOff= new LightOffCommand(livingRoomLight);
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
StereoOnWithCDComand stereoOnWithCDComand = new StereoOnWithCDComand(livingRoomStereo);
StereoOffWithCDComand stereoOffWithCDComand = new StereoOffWithCDComand(livingRoomStereo);
//提供开关宏命令
MacroCommand partyOn = new MacroCommand(new Command[]{livingRoomLightOn, kitchenLightOn});
MacroCommand partyOff = new MacroCommand(new Command[]{livingRoomLightOff, kitchenLightOff});
//3、封装命令到Invoker协调者
remoteControl.setCommand(0,livingRoomLightOn,livingRoomLightOff);
remoteControl.setCommand(1,kitchenLightOn,kitchenLightOff);
remoteControl.setCommand(2,stereoOnWithCDComand,stereoOffWithCDComand);
remoteControl.setCommand(3,partyOn,partyOff);
System.out.println(remoteControl);
//4、Invoker,协调者发出执行命令的请求
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
//撤销命令
System.out.println("撤销命令:");
remoteControl.undoButtonWasPushed();
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
//执行宏命令
remoteControl.onButtonWasPushed(3);
remoteControl.offButtonWasPushed(3);
}
}
总结:
命令模式封装的是请求:其实是将计算打包。可以将动作的执行顺序管理起来。解耦的是请求命令的对象以及执行命令的对象。
应用场景:命令模式封装的是请求,在实际应用中,请求一般都是跟队列相关。那么命令模式应用场景其实还是蛮多的。比如说工作队列,日程安排,线程池这些。日志请求也是一个方面,设置还能记录撤销,根据日志队列取出命令请求,还可以撤销命令达到恢复的目的。