:::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 构建命令的调用者
```java
public 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);
}
@Override
public void execute() {
television.playCctv1();
}
}
public class CCTV2Command extends Command {
public CCTV2Command(Television television) {
super(television);
}
@Override
public void execute() {
television.playCctv2();
}
}
public class CCTV3Command extends Command {
public CCTV3Command(Television television) {
super(television);
}
@Override
public void execute() {
television.playCctv3();
}
}
public class CCTV4Command extends Command {
public CCTV4Command(Television television) {
super(television);
}
@Override
public void execute() {
television.playCctv4();
}
}
public class CCTV5Command extends Command {
public CCTV5Command(Television television) {
super(television);
}
@Override
public 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组装执行。