在程序中,我们每敲一行命令都会得到回复,比如SQL命令。
这样的例子非常多,例如,电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。
定义与特点
- 定义
将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。命令模式通过这种封装的方式实现将客户端和接收端解耦。
- 详细概述
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
命令模式也支持可撤销的操作。命令模式通过这种封装的方式实现将客户端和接收端解耦。命令模式属于行为型模式。
参与角色
- 抽象命令接口(Command):定义命令的接口,拥有执行命令的抽象方法 execute()。
- 具体的命令对象(ConcreteCommand):抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
- 接收者对象(Receiver):命令接收者,命令的实际执行者。
- 命令发送者(Invoker):请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
- 结构类图
模式实现分析
由于物联网快速发展,BAT纷纷布局智能家居市场,假设现在你买了台百度制造的小度音箱,他可以控制你家的电视....
现在我们使用命令模式来实现该程序
创建抽象命令接口
//抽象的命令接口 interface Command { //执行命名的接口 void execute(); }
命令接受者
//电视 class TV { public void tvOn() { System.out.println("电视打开了!!"); } public void tvOff() { System.out.println("电视关上了!!"); } }
具体的命令接口(开、关电视)
//开电视命令 class TvOnCommand implements Command{ private TV tv; public TvOnCommand(TV tv) { this.tv = tv; } @Override public void execute() { tv.tvOn(); } } //关电视命令 class TvOffCommand implements Command{ private TV tv; public TvOffCommand(TV tv) { this.tv = tv; } @Override public void execute() { tv.tvOff(); } }
传递命令对象
class XiaoDu { private Command command; //设置具体的命令,依赖注入 public void setCommand(Command command) { this.command = command; } //执行命令 public void doCommand() { command.execute(); } }
调用客户端
public class CommandClient { public static void main(String[] args) { // 创建小度同学 XiaoDu xiaoDu = new XiaoDu(); // 创建具体的等对象,相当于具体的命令接受者 TV tv = new TV(); // 创建了开电视的命令,你就是命令的发起者 System.out.println("小度同学帮我把电视开一下!"); TvOnCommand tvOnCommand = new TvOnCommand(tv); // 小度同学接受到了你发出的命令,并执行命令 xiaoDu.setCommand(tvOnCommand); xiaoDu.doCommand(); } } //运行结果 小度同学帮我把电视开一下! 电视打开了!!
上面的例子仅仅是实现单个命令的的命令模式,而命令模式是可以相当复杂的,就比如说,你让小度同学帮你打电视机,并且打开灯,并且帮你打开窗帘,并且打开空调等等,这时候我们可以将多个命令存储起来,然后一次性执行。
通过上面的案例,我们知道命令模式就是把命令封装成对象,然后将动作请求者与动作执行者完全解耦,比如遥控器的按钮和电器没有任何关系。也就是说命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。
总结
优点
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
其缺点是:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。
宏命令
将多个命令存储起来,发出执行宏命令请求的之后执行存储的所有命令。
命令撤销
在我们发出某个请求并执行之后,将命令的执行状态进行保存,如果我们想要回归执行这个命令之前的状态,我们就可以通过命令撤销的方式回归到之前的状态。
队列
还记得定义中提到了队列,命令模式如何用于队列呢,比如饭店有很多个点菜的地方,有一个做菜的地方,把点菜看作命令,做菜看作命令执行者,不断有人点菜就相当于把菜加入队列,对于做菜的只管从队列里面取,取一个做一个。
日志
定义中还提到了日志,日志一般用于记录用户行为,或者在异常时恢复时用的,比如每个命令现在包含两个方法,一个执行execute,一个undo(上例中为了方便大家理解,没有写undo),我们可以把用户所有命令调用保存到日志中,比如用户操作不当了,电器异常了,我们只需要把日志中所有的命令拿出来执行一遍undo就完全恢复了。