:::info 此模式在日常开发中使用频率不高,但关键时刻是能起大作用的。 :::
含义
将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或记录日志,以及支持可撤销的操作。
使用场景
- 当需要将各种执行的动作抽象出来,使用时通过不同的参数来决定执行哪个对象
- 当某个或者某些操作需要支持撤销的场景
- 当要对操作过程记录日志,以便后期通过日志将操作过程重新做一遍时
-
UML
角色结构
Command:是一个接口或者抽象类,定义一个命令。
- ConcreteCommand:具体的执行命令,他们需要实现Command接口或者继承抽象类。
- Receiver:真正执行命令的角色,那些具体的命令引用它,让它完成命令的执行。
Invoker:负责按照客户端的指令设置并执行命令,像命令的撤销,日志的记录等功能都要在此类中完成。
优点
调用操作与具体执行者解耦。
- 添加一个命令非常容易。
-
缺点
-
业务场景
当我们看电视的时候,有时候插播广告就很烦人,于是我们就在广告的时候去切到其他频道去看,遥控器上既有后退按钮也有前进按钮,对应的是我们的undo和redo操作。
代码示例
Command 命令抽象类
所有具体的命令都会实现此接口,Invoker只认识此接口,该类持有了命令的执行者TV
public abstract class Command {/*** 命令接收者:电视机*/protected Television television;public Command(Television television) {this.television = television;}/*** 执行命令*/public abstract void execute();}
Television 电视机对象:提供了播放不同频道的方法
```java public class Television { public void playCctv1() {
System.out.println("--CCTV1--");
}
public void playCctv2() {
System.out.println("--CCTV2--");
}
public void playCctv3() {
System.out.println("--CCTV3--");
}
public void playCctv4() {
System.out.println("--CCTV4--");
}
public void playCctv5() {
System.out.println("--CCTV5--");
}
}
<a name="EuRiG"></a>#### Invoker 构建命令的调用者```javapublic class Invoker {/*** 当前播放*/Command command;/*** 播放记录*/Stack<Command> unStack = new Stack<>();Stack<Command> reStack = new Stack<>();/*** 切换卫视*/public void switchCommand(Command command) {if (this.command != null) {unStack.push(this.command);}this.command = command;command.execute();}/*** 遥控器返回命令*/public void undo() {if (unStack.isEmpty()) {return;}//获取上一个播放某卫视的命令Command command = unStack.pop();reStack.push(this.command);this.command = command;command.execute();}/*** 遥控器恢复命令*/public void redo() {if (reStack.isEmpty()) {return;}//获取上一个播放某卫视的命令Command command = reStack.pop();unStack.push(this.command);this.command = command;command.execute();}}
CCTV1Command… 命令的实现者
public class CCTV1Command extends Command {public CCTV1Command(Television television) {super(television);}@Overridepublic void execute() {television.playCctv1();}}
public class CCTV2Command extends Command {public CCTV2Command(Television television) {super(television);}@Overridepublic void execute() {television.playCctv2();}}
public class CCTV3Command extends Command {public CCTV3Command(Television television) {super(television);}@Overridepublic void execute() {television.playCctv3();}}
public class CCTV4Command extends Command {public CCTV4Command(Television television) {super(television);}@Overridepublic void execute() {television.playCctv4();}}
public class CCTV5Command extends Command {public CCTV5Command(Television television) {super(television);}@Overridepublic void execute() {television.playCctv5();}}
Client
public class Client {public static void main(String[] args) {//创建一个电视机Television tv = new Television();//创建一个遥控器Invoker invoker = new Invoker();invoker.switchCommand(new CCTV1Command(tv));invoker.switchCommand(new CCTV2Command(tv));invoker.switchCommand(new CCTV3Command(tv));invoker.switchCommand(new CCTV4Command(tv));invoker.switchCommand(new CCTV5Command(tv));System.out.println("------undo--------");//模拟遥控器返回键invoker.undo();invoker.undo();invoker.undo();invoker.undo();System.out.println("------redo--------");invoker.redo();invoker.redo();invoker.redo();invoker.redo();}}
输出
--CCTV1----CCTV2----CCTV3----CCTV4----CCTV5--------undo----------CCTV4----CCTV3----CCTV2----CCTV1--------redo----------CCTV2----CCTV3----CCTV4----CCTV5--
技术要点总结
- Command 接口非常简单,通常只有一个execute方法,如果要支持撤销操作的话,再加一个unexecute方法,实现数据回滚等。
- 每个具体的命令类内部封装了实际执行命令的那个类(Recevier),或者那些类,以及执行需要的数据,上面的示例主要是所有的命令类都是操作同一个对象,否则下沉至命令实现类持有。
- 每个具体命令类只完成一个请求,有多少个请求就有多少个命令。
- Invoker类只认识接口Command,其他的都不认识
- 客户端类负责生成命令,并通过Invoker组装执行。
