正式定义
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
实例讲解
让我们通过一个演示例子来对命令模式有一个直观的了解。
假设我们要设计一个遥控器,这个遥控器具有一个可编程的插槽,插槽有对应的开关按钮,并且还有一个整体的撤销按钮。假设我们现在要用这个遥控器来控制车库门,车库门类如下所示:
package com.star.command;
/**
* 车库门类,它具有开启、关闭、开灯、关灯等功能
*/
public class GarageDoor {
public void up(){ System.out.println("Garage Door up");}
public void down(){ System.out.println("Garage Door down"); }
public void stop(){ System.out.println("Garage Door stop"); }
public void lightOn(){ System.out.println("Garage Door LightOn");}
public void lightOff(){ System.out.println("Garage Door LightOff");}
}
车库门是接受命令的对象,我们称它为命令的接收者。接下来我们创建一个车库门打开的命令类,这个对象接受一个车库门对象,调用它的up方法来打开车库门,同时调用lightOn方法打开车库的灯。
package com.star.command;
/**
* 车库门打开命令。
* 是一个Command。
* @see Command
* 接收一个车库门对象,并调用其功能方法来实现自己的execute方法
*/
public class GarageDoorOpenCommand implements Command {
GarageDoor garageDoor;
public GarageDoorOpenCommand(GarageDoor garageDoor) {
this.garageDoor = garageDoor;
}
public void execute() {
garageDoor.up();
garageDoor.lightOn();
}
}
GarageDoorOpenCommand是一个具体的命令,同时我们注意到GarageDoorOpenCommand实现了Command接口,Command接口内容如下:
package com.star.command;
/**
* 命令接口。
* 定义了统一的execute()方法,所有的命令类都需要继承该接口
*/
public interface Command {
public void execute();
}
Command接口是一个抽象的命令,这个接口只有一个execute方法。让车库门打开命令实现一个命令接口是有好处的,这样可以让命令的调用者与命令的接收者——车库门类解耦,以后我们需要增加新的命令的时候,比如说添加一个卧室灯控的命令,那么只需要让这个命令实现Command接口,而不需要修改命令调用者的代码,这体现了“对拓展开放,对修改关闭”的OO设计原则。
现在我们有了车库门、车库门打开命令,现在我们需要一个遥控器来操控这个命令:
package com.star.command;
/**
* 简单遥控器类。
* 该类可以考虑构造成单例模式,毕竟遥控器一个就够了。
*/
public class SimpleRemoteControl {
/**
* 使用command接口,来实现调用者与具体命令的解耦
*/
Command command;
//不使用构造器引入command的原因是,构造器只能在构造对象时使用一次,之后便无法更改。
public SimpleRemoteControl() {}
/**
* setter方法,用于设置命令,并且后面可以再次调用该方法更改command。
* @param command 需要执行的命令
*/
public void setCommand(Command command) {
this.command = command;
}
//模拟遥控器按下的操作,调用命令的execute方法。
public void buttonWasPressed(){
command.execute();
}
}
这个遥控器持有一个命令的引用,并用setCommand()方法来设置命令。当遥控器被按下,即buttonWasPressed()方法执行时,遥控器会调用传进来的命令的execute方法完成功能操作。所以遥控器是命令的调用者。
现在,让我们来测试一下我们的遥控器是否起作用:
package com.star.command;
/**
* 遥控器测试类
* 在命令模式中担当“客户”这一角色,负责创建命令,并将一个接收者传入命令中。
*/
public class RemoteControlTest { //客户
public static void main(String[] args) {
//创建遥控器
SimpleRemoteControl remoteControl=new SimpleRemoteControl(); //调用者
//创建一个车库门对象,也就是命令的接收者
GarageDoor garageDoor=new GarageDoor(); //接收者
//创建车库门打开命令,传入一个车库门对象
GarageDoorOpenCommand command=new GarageDoorOpenCommand(garageDoor); //命令
//为遥控器设置命令——把命令传给调用者。
remoteControl.setCommand(command);
//按下遥控器
remoteControl.buttonWasPressed();
}
}
/* 输出为:
Garage Door up
Garage Door LightOn
Process finished with exit code 0
*/
在测试类中,我们首先创建了一个调用者,也就是遥控器。接着我们创建了车库门对象和车库门打开命令对象,他们分别是接收者和命令。我们将接收者传递进了命令中,以便在调用者调用命令时执行具体的操作。然后我们使用setCommand()方法将命令传递给了遥控器,最后,按下遥控器的按钮。从输出中我们可以看到,当我们按下按钮的时候,车库门打开了,车库灯也打开了。RemoteControlTest类创建了命令和接收者,并将接收者传入到命令当中。我们称它为客户。
模式总结
从上面的例子我们可以看到,我们用一个“打开车库门命令”加载按钮插槽,同样我们可以用setCommand()命令加载另一个命令,如“打开卧室灯命令”。遥控器插槽根本不在乎所拥有的的是什么命令,只要该命令对象实现了Command接口就可以了。
命令模式的类图:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLFJUJClTJ1UUJCJUJxIUJ3UUJGhTJDJUJ1UUJxEUJ4EUJ2UUJ0EUJCJUJ0UUJEJUJxkTJ1UUJvwVbvNmLuRGZ19Gbj5CdrJmLwpWMvpHc3R2bvw1LcpDc0RHaiojIsJye.png)
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
完整的实例已经上传到Github,欢迎大家克隆学习。Github项目地址:https://github.com/Dodozhou/DesignPatternLearning