1. 定义
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作
这个模式我自己感觉理解不深,敲代码时没怎么遇见过这种设计模式。所以引用JAVA设计模式(15):行为型-命令模式(Command)中的一段话来进一步解释概念:
命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法
2. 类图
从类图中我们可以了解到,命令模式主要分为了四块:
- 命令(Command)
- 命令接受者(Receiver)
- 命令调用者(Invoker)
- 客户(Client)
整个流程可以看成,客户通过命令调用者来调用命令,而命令再调动命令接受者来处理。
如果不按照命令模式,那客户就要直接想办法调用Receiver的方法,耦合度大大增加。下面我们用场景代码来进一步说明
3. 场景+代码
场景:
现在我要设计一个遥控器类,上面有7对按钮,分别对应不同的功能(例如风扇开和风扇关、电灯开和电灯关),该如何设计?
我们先准备几个“功能类”,分别代码遥控器按钮要执行的功能:
/**
* 电灯
*/
public class Light {
public void on() {
System.out.println("电灯打开了");
}
public void off() {
System.out.println("电灯关闭了");
}
}
/**
* 车库门
*/
public class GarageDoor {
public void on() {
System.out.println("车库门打开了");
}
public void off() {
System.out.println("车库门关闭了");
}
}
然后我们按最简单的逻辑,直接来设计这个遥控器:
public class OldRemoteControl {
//遥控器控制的家居-电视
Light light;
//遥控器控制的家居-车库门
GarageDoor garageDoor;
……
public OldRemoteControl() {
light = new Light();
garageDoor = new GarageDoor();
……
}
/**
* 按下某一开按钮
*/
public void onButtonWasPressed(int i) {
//假设遥控器第一个按钮控制电灯
if(i == 0){
light.on();
}
//假设遥控器第二个按钮控制车库门
else if(i == 1){
garageDoor.on();
}
……
}
/**
* 按下某一关按钮
*/
public void offButtonWasPressed(int i) {
//假设遥控器第一个按钮控制电灯
if(i == 0){
light.off();
}
//假设遥控器第二个按钮控制车库门
else if(i == 1){
garageDoor.off();
}
……
}
}
这样写没什么问题。遥控器可以很正常的运行。
但是,我们很快发现:
- 每次遥控器按钮功能改变时,我们都需要改动代码,这违反了我们之前所说的“开闭设计原则”:类应该对拓展开发,对修改关闭。
- 遥控器和功能类的依赖太深,换句话说,这两个类耦合很严重。
如何改进?现在就是我们用命令模式的时刻啦~
代码
之前的定义讲过,命令模式的核心在于Command
类,“命令发送者”调用“命令类”,而“命令类”调用“命令接受者”,从而降低发送者和接收者的耦合度,接下来看它的详细实现:
command
/**
* 命令接口
*/
public interface Command {
/**
* 执行命令
*/
public void execute();
}
/**
* 电灯打开命令类
*/
public class LightOnCommand implements Command{
/**receiver*/
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute(){
light.on();
}
}
/**
* 电灯关闭命令类
*/
public class LightOffCommand implements Command{
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute(){
light.off();
}
}
/**
* 车库门打开命令类
*/
public class GarageDoorOnCommand implements Command{
private GarageDoor garageDoor;
public GarageDoorOnCommand(GarageDoor garageDoor) {
this.garageDoor = garageDoor;
}
public void execute(){
garageDoor.on();
}
}
/**
* 车库门关闭命令类
*/
public class GarageDoorOffCommand implements Command{
private GarageDoor garageDoor;
public GarageDoorOffCommand(GarageDoor garageDoor) {
this.garageDoor = garageDoor;
}
public void execute(){
garageDoor.off();
}
}
receiver
/**
* 电灯
*/
public class Light {
public void on() {
System.out.println("电灯打开了");
}
public void off() {
System.out.println("电灯关闭了");
}
}
/**
* 车库门
*/
public class GarageDoor {
public void on() {
System.out.println("车库门打开了");
}
public void off() {
System.out.println("车库门关闭了");
}
}
invoker
我们现在来看一下,加了Command
后的遥控器类:
/**
* 遥控器,有多个指令
*/
public class RemoteControl {
/**开命令按钮*/
Command[] onCommands;
/**关命令按钮*/
Command[] offCommands;
/**空对象*/
public static Command noCommand = new NoCommand();
public RemoteControl() {
//设置遥控器有7对按钮,分别对应打开、关闭
onCommands = new Command[7];
offCommands = new Command[7];
//将所有按钮初始化为空按钮,反正空指针报错
for(int i = 0 ; i < 7 ; i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
/**
* 设置一对按钮
*/
public void setCommand(int i,Command onCommand,Command offCommand) {
onCommands[i] = onCommand;
offCommands[i] = offCommand;
}
/**
* 按下某一开按钮
*/
public void onButtonWasPressed(int i) {
onCommands[i].execute();
}
/**
* 按下某一关按钮
*/
public void offButtonWasPressed(int i) {
offCommands[i].execute();
}
}
/**
* 一般invoker,只对应一个Command。此处给出作demo
*/
public class SimpleRemoteControl {
Command command;
public void setCommand(Command command) {
this.command = command;
}
public void buttonWasPressed() {
command.execute();
}
}
这段代码用到了“空对象”的概念。
我们想象一下,假如遥控器只设置了前两对按钮的功能,那为了我们按到其余按钮时程序不出错,需要进行判空操作:
public void onButtonWasPressed(int i) {
if(onCommands[i] != null)
onCommands[i].execute();
}
这样做麻烦很多,我们需要谨慎的判断各种可能出现空指针的情况,另外一旦出现空指针,程序都会被中断,而且没有任何空对象的提示。
为了改善这种情况,我们定义一个空对象命令类:
/**
* 空指令,null的取代对象
*/
public class NoCommand implements Command{
@Override
public void execute() {
System.out.println("什么都不做……");
}
@Override
public void undo() {
System.out.println("什么都不做……");
}
}
再在命令调用类(遥控器)初始化时,指定所有按钮为空命令
public RemoteControl() {
//设置遥控器有7对按钮,分别对应打开、关闭
onCommands = new Command[7];
offCommands = new Command[7];
//将所有按钮初始化为空按钮,反正空指针报错
for(int i = 0 ; i < 7 ; i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
这样,便不需要再进行空判断了,而且即使按错了空功能的按钮,也会进行提示。
client
最后,由客户,也就是遥控器操纵者来发送命令,进行测试:
/**
* 遥控器客户
*/
public class RemoteControlClient {
public static void main(String[] args) {
//客户创建一组命令对象,并将其放入调用者中(想要的遥控器)
RemoteControl remoteControl = generateControl();
//客户发送命令请求(操纵遥控器)
remoteControl.onButtonWasPressed(0);
System.out.println("=================");
remoteControl.onButtonWasPressed(1);
}
/**
* 创建一个遥控器
*/
public static RemoteControl generateControl(){
Light light = new Light();
GarageDoor garageDoor = new GarageDoor();
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
Command garageDoorOn = new GarageDoorOnCommand(garageDoor);
Command garageDoorOff = new GarageDoorOffCommand(garageDoor);
RemoteControl remoteControl = new RemoteControl();
remoteControl.setCommand(0, lightOn, lightOff);
remoteControl.setCommand(1, garageDoorOn,garageDoorOff);
return remoteControl;
}
}
拓展
撤销功能
现在我想要遥控器有撤销功能,即按下撤销键,把最近的一次操作撤销掉,该如何做?
首先,给每个命令类都增加一个撤销的方法:
/**
* 命令接口
*/
public interface Command {
/**
* 执行命令
*/
public void execute();
/**
* 撤销命令
*/
public void undo();
}
/**
* 电灯打开命令类
*/
public class LightOnCommand implements Command{
/**receiver*/
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
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;
}
public void execute(){
light.off();
}
@Override
public void undo() {
light.on();
}
}
/**
* 车库门打开命令类
*/
public class GarageDoorOnCommand implements Command{
private GarageDoor garageDoor;
public GarageDoorOnCommand(GarageDoor garageDoor) {
this.garageDoor = garageDoor;
}
public void execute(){
garageDoor.on();
}
@Override
public void undo() {
garageDoor.off();
}
}
/**
* 车库门关闭命令类
*/
public class GarageDoorOffCommand implements Command{
private GarageDoor garageDoor;
public GarageDoorOffCommand(GarageDoor garageDoor) {
this.garageDoor = garageDoor;
}
public void execute(){
garageDoor.off();
}
@Override
public void undo() {
garageDoor.on();
}
}
然后用一个变量,记录每次命令调用者(遥控器)的最后一次操作
public class RemoteControl {
/**开命令按钮*/
Command[] onCommands;
/**关命令按钮*/
Command[] offCommands;
/**最后一次执行的命令*/
Command lastCommand;
/**空对象*/
public static Command noCommand = new NoCommand();
public RemoteControl() {
//设置遥控器有7对按钮,分别对应打开、关闭
onCommands = new Command[7];
offCommands = new Command[7];
//将所有按钮初始化为空按钮,反正空指针报错
for(int i = 0 ; i < 7 ; i++){
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
lastCommand = noCommand;
}
/**
* 设置一对按钮
*/
public void setCommand(int i,Command onCommand,Command offCommand) {
onCommands[i] = onCommand;
offCommands[i] = offCommand;
}
/**
* 按下某一开按钮
*/
public void onButtonWasPressed(int i) {
lastCommand = onCommands[i];
if(onCommands[i] != null)
onCommands[i].execute();
}
/**
* 按下某一关按钮
*/
public void offButtonWasPressed(int i) {
lastCommand = offCommands[i];
offCommands[i].execute();
}
/**
* 撤销最近一次的操作
*/
public void cancelButtonWasPressed() {
lastCommand.undo();
}
}
这样便成功了,如果想多次撤销之前的命令,同理,只需设置一个集合存储之前操作的所有命令,再一一调用undo()
方法即可。
多功能综合按钮
看到之前的代码,可能有人会疑惑“接受者”存在的意义。为什么不直接在命令类的execute()
方法中直接实现所有的逻辑呢?
之前设计的命令类,都是“简单式”的命令类。即它只懂得调用一个接受者的一个行为。当我们要实现“复杂式”的命令类,调用多个接受者行为时,“接受者”存在便很重要,它可以帮助我们解耦。看下面的例子。
现在我嫌遥控器按钮功能太单一,例如我想要按一个按钮同时开灯和开车库门,如何设计这种“批处理”按钮?
设计一个“批处理命令类”
/**
* 复杂命令,由多个命令组成
*/
public class MacroCommand implements Command{
private 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 RemoteControlClient {
public static void main(String[] args) {
//客户创建一组命令对象,并将其放入调用者中(想要的遥控器)
RemoteControl remoteControl = generateControl();
//客户发送命令请求(操纵遥控器)
System.out.println("========测试开灯=========");
remoteControl.onButtonWasPressed(0);
System.out.println("========测试开车库门=========");
remoteControl.onButtonWasPressed(1);
System.out.println("=========测试综合按钮========");
remoteControl.onButtonWasPressed(2);
System.out.println("=========测试撤销按钮========");
remoteControl.cancelButtonWasPressed();
}
/**
* 创建一个遥控器
*/
public static RemoteControl generateControl(){
Light light = new Light();
GarageDoor garageDoor = new GarageDoor();
//创建“傻瓜式”命令
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
Command garageDoorOn = new GarageDoorOnCommand(garageDoor);
Command garageDoorOff = new GarageDoorOffCommand(garageDoor);
//创建“批处理”命令
Command[] maxOn = new Command[]{lightOn,garageDoorOn};
Command[] maxOff = new Command[]{lightOff,garageDoorOff};
MacroCommand partyOn = new MacroCommand(maxOn);
MacroCommand partyOff = new MacroCommand(maxOff);
//将命令与调用者绑定
RemoteControl remoteControl = new RemoteControl();
remoteControl.setCommand(0, lightOn, lightOff);
remoteControl.setCommand(1, garageDoorOn,garageDoorOff);
remoteControl.setCommand(2, partyOn,partyOff);
return remoteControl;
}
}/**Output:
========测试开灯=========
电灯打开了
========测试开车库门=========
车库门打开了
=========测试综合按钮========
电灯打开了
车库门打开了
=========测试撤销按钮========
电灯关闭了
车库门关闭了*/
3. 用途
命令模式运用于:线程池、工作队列和日志请求等等
本文总结自
《Head First 设计模式》第六章:命令模式