首先看看命令模式的定义:命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持撤销的操作。
所谓参数化,我的理解是实际执行的对象,比如light(电灯)、strereo(音响),他们存在执行动作的方法,并且作为命令对象的成员变量
队列、日志这两样是命令模式的应用直接扩展
为什么需要命令模式:
在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。
命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。
假如有这样一种情景,我现在需要打开风扇,自然我需要一个遥控器,很明显这里的关系是:我——遥控器—>命令——风扇,而遥控器发出命令去控制风扇,其中,遥控器是请求者,风扇是执行者,遥控器不知道怎么让风扇转起来,他只知道发出命令,即上面的动机。
也可以这样子想象:我在餐厅里点菜,服务员他帮我转达请求,厨师帮我炒菜,而服务员是不知道怎么去炒,他仅仅转达请求而已——完全解耦了
先看看类图:
其中,我们可以把invoker看作遥控器,receiver看作动作的执行对象,即light之类,我们现在直接看一个支持撤销命令的代码并不是很难,所以直接上这个,来自《Head First 设计模式》:
这里给出一个接口和两个代表性的实体类(其实实体类有light、fan、cellingfan(这个是实现除开关两种可能性以外的多种可能性的撤销方法)),类里面有并没有罗列出来的类,但其实是差不多的,应该不会造成阅读障碍
先看实体类:他们各自有自己的行为方法,开关、换高低速(更好地表现撤销功能)
public class Light {
public void On(){
System.out.println("Light-----On");
}
public void Off(){
System.out.println("Light-----Off");
}
}
public class Fan {
public void On(){
System.out.println("Fan-----On");
}
public void Off(){
System.out.println("Fan-----Off");
}
}
public class CellingFan{
public static final int High = 3;
public static final int MEDIUM = 2;
public static final int LOW = 1;
public static final int OFF = 0;
String location;
int speed;
public CellingFan(String location) {
this.location = location;
speed = OFF;
}
public void high(){
System.out.println("Speed-----High");
speed = High;
}
public void medium(){
System.out.println("Speed-----Medium");
speed = MEDIUM;
}
public void Low(){
System.out.println("Speed-----Low");
speed = LOW;
}
public void Off(){
System.out.println("Speed-----Off");
speed = OFF;
}
public int getSpeed() {
return speed;
}
}
实体类并没有什么特别指出,看上去好轻松,接下来是封装好的命令抽象,这里的命令抽象也很简单,只支持两种情况:开、关,开了撤销就是关,关了撤销就是开
public interface Command {
public void execute();
public void undo();
}
public class LightOnCommand implements Command {
Light light;
public void setLight(Light light) {this.light = light;}
@Override
public void execute() {light.On();}
@Override
public void undo() {light.Off();}
}
public class LightOffCommand implements Command {
Light light;
public void setLight(Light light) {this.light = light;}
@Override
public void execute() {light.Off();}
@Override
public void undo() {light.On();}
}
//这里就不列举fan的命令对象了
这里的另外一个命令是支持多种情况的:这里只列举一个高速的命令,其他大同小异
public class CellingFanHighCommand implements Command {
CellingFan cellingFan;
int prevSpeed;
public void setCellingFan(CellingFan cellingFan) {
this.cellingFan = cellingFan;
}
@Override
public void execute() {
prevSpeed = cellingFan.getSpeed();
cellingFan.high();
}
@Override
public void undo() {
switch(prevSpeed){
case CellingFan.High:cellingFan.high();break;
case CellingFan.LOW:cellingFan.Low();break;
case CellingFan.MEDIUM:cellingFan.medium();break;
case CellingFan.OFF:cellingFan.Off();break;
default:break;
}
}
}
可以看到这里有一个判断:之前的速度是什么,便让执行者执行之前的命令,我们也提到过,执行者就是那堆实体类,他们知道如何执行任务
我们还需要一个空对象来填充那些null的位置
public class noCommand implements Command {
@Override
public void execute() {}
@Override
public void undo() {}
}
到了这里,我们定义了实体类,把命令抽象了出来,还需要一个遥控器
我们的遥控器根据不同的设备设置开关按钮,还有一个撤销按钮,当按下开关、转速按钮的同时把执行这一动作的命令保存到撤销命令里面,这样想撤销的时候就可以调用undoButtonPushed()方法来执行上一次的命令的undo方法,不过要用之前得设置按钮
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
//开关灯
//开关风扇
//转速高速、关
//转速中速、关
//转速低速、关 共10个按钮
public RemoteControl(){
onCommands = new Command[5];//负责开的
offCommands = new Command[5];//负责关的
//引入空对象这一设计理念,关于空对象会在java中的类型转换博客中提及
Command noCommand = new noCommand();
for(int i = 0;i<5;i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot,Command onCommand,Command OffCommand){
onCommands[slot] = onCommand;
offCommands[slot] = OffCommand;
}
public void onButtonWasPushed(int slot){
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot){
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPushed(){
undoCommand.undo