在我们生活中,有很多命令模式的例子:比如我们经常使用的电视遥控器,我们在用它来控制电视的时候,遥控器其实就是一个请求者的角色,它并没有做任何实际性的工作,真正的工作是电视机来做的,在整个控制的过程中,遥控器发出指令,电视机来真正的处理收到的指令,遥控器发出的请求就是命令,正是这个请求的命令,将请求者和执行者进行了分离,达到了我们远程控制电视的目的。在我们的软件编程中,命令模式也是利用了这种思想,用封装的命令对象将请求者和真正的执行者进行了分离,从而减少了系统的耦合,也增加了系统的扩展性。
命令模式虽然本质上是分离了请求者和执行者,但是其设计重点还在命令对象本身,我们抽象出的命令对象并不像外观模式中的外观类一样只是一个门面,而是承载了更多的命令相关的功能,也就是说,命令对象的不同实现已经代表了不同的命令,在处理命令的逻辑时,是由命令对象来确定具体是调用真正实现者的什么方法,而不是由调用者指定,这一点就非常的重要。而且命令模式除了调用逻辑外,在很多场景中还会伴随着撤销和重做等各种功能,具体的实现也是由命令对象来控制调用的。下面我们就来详细了解一下:
命令模式结构说明
- Command:定义命令的接口,声明执行的方法。
- ConcreteCommand:命令对象的实现,在这个过程中,命令对象并不会实现具体的功能,具体的功能是接受者实现的,但是命令对象会决定调用接受者的哪个操作,以此来达到扩展性。
- Receiver:接收者:真正实现业务逻辑的对象。
- Invoker:命令的请求者,通常会持有命令对象,并调用命令对象来执行操作。
命令模式示例
我们来简单模拟一个命令模式的使用场景:我们日常生活中经常会用到播放器播放视频文件,我们只需要在页面上点击播放的按钮或者暂停的按钮,就能控制播放器的行为,在这个过程中,面板上面的按钮只是一个请求者,它会将命令传递给播放器的内核来具体的实施,是一个典型的命令模式场景,同时为了演示命令模式撤销和重做的实现,我们也加上暂停和播放的撤销和重新执行的行为。示例代码如下:
1. 模拟一个播放器内核的真正实现类,实现开始和暂停两个行为
/**
* 播放器真正实现类 —— 接受者
*/
public class Player {
public void start(){
System.out.println("开始播放");
}
public void pause(){
System.out.println("暂停播放");
}
}
2. 创建一个命令的接口,定义执行和撤销的方法
/**
* 命令接口
*/
public interface Command {
/**
* 执行方法
*/
void execute();
/**
* 撤销方法
*/
void unExecute();
}
3. 实现开始和暂停两个具体的命令对象,在具体实现中,因为要调用播放器的真正实现业务的方法,我们让命令对象持有播放器的对象
/**
* 开始播放命令
*/
public class StartCommand implements Command{
private Player player;
public StartCommand(Player player){
this.player = player;
}
@Override
public void execute() {
player.start();
}
@Override
public void unExecute() {
player.pause();
}
}
/**
* 暂停播放命令
*/
public class PauseCommand implements Command {
private Player player;
public PauseCommand(Player player) {
this.player = player;
}
@Override
public void execute() {
player.pause();
}
@Override
public void unExecute() {
player.start();
}
}
4. 实现一个ControlPanel类来模拟控制界面,也就是调用者角色
在ControlPanel中,我们创建两个栈executeStack和unExecuteStack,用来存放执行的对象和撤销的对象,每执行一个对象,我们就将其压入executeStack中,在后面撤销的时候,我们先从executeStack中弹出一个对象,执行unExecute()方法后压入unExecuteStack中,等待重新执行,同样的,重新执行的时候我们从unExecuteStack中弹出一个对象,执行execute()方法后又压入executeStack中,以实现撤销和重做等业务逻辑。具体实现如下:
/**
* 控制面板类——调用者
*/
public class ControlPanel {
/**
* 命令执行栈
*/
private final Stack<Command> executeStack = new Stack<>();
/**
* 命令撤销栈
*/
private final Stack<Command> unExecuteStack = new Stack<>();
public void execute(Command command) {
command.execute();
executeStack.push(command);
}
/**
* 撤销
*/
public void unExecute() {
if (executeStack.isEmpty()) {
System.out.println("没有可撤销的命令");
return;
}
Command command = executeStack.pop();
command.unExecute();
unExecuteStack.push(command);
}
/**
* 重新执行
*/
public void reExecute() {
if (unExecuteStack.isEmpty()) {
System.out.println("没有可撤销的命令");
return;
}
Command command = unExecuteStack.pop();
command.execute();
executeStack.push(command);
}
}
5. 编写一个测试类,验证实现逻辑
public class Client {
@Test
public void testCommand(){
Player player = new Player();
ControlPanel panel = new ControlPanel();
System.out.println("---------------执行命令---------------");
panel.execute(new StartCommand(player));
panel.execute(new PauseCommand(player));
System.out.println("---------------撤销命令---------------");
panel.unExecute();
panel.unExecute();
System.out.println("---------------重新执行命令---------------");
panel.reExecute();
panel.reExecute();
}
}
结果如下:
---------------执行命令---------------
开始播放
暂停播放
---------------撤销命令---------------
开始播放
暂停播放
---------------重新执行命令---------------
开始播放
暂停播放
6. 示例类图:
后记
个人总结,欢迎转载、评论、批评指正