简介
命令模式(Command Pattern)属于设计模式中的行为型模式。命令模式实现了施令者与具体命令的解耦,并且可以实现撤销等命令相关功能。
定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
命令模式的核心是命令,何为命令?其实命令在日常用于中叫请求,调用一个类的方法,其实就是给类发一个命令让它去执行这个方法,命令模式就是将原本的函数调用封装到类中,由方法级别提升到类级别,从而实现请求者与执行者的解耦,并且可以提供更多的功能,譬如撤销,记录日志等。
角色
命令模式的角色有抽象命令角色,具体命令角色,命令接收者角色及命令调用者角色。
抽象命令:定义命令的通用接口,用于提供给调用者统一的调用API 具体命令:实现具体的命令执行逻辑 命令接收者:真正的逻辑实现地方,(命令提供给调用方一个统一界面,真正逻辑并不在命令里实现,如果没有命令接收者,具体命令就需要充当命令接收者的角色) 调用者:请求的发起方
其实应该还有一个协调者角色,它负责初始化具体的命令,设置到调用者中,不过这个一般在main 函数中实现,不列到此处。
模式说明
既然是将对象请求封装为命令,那么所有的代码其实都可以封装成命令模式,区别只是有没有必要。在具体实践中,用户界面相关使用最多,因为命令模式支持的撤销、日志、记录等功能都特别符合用户需求。
在浏览器或手机APP 中,都会提供返回功能,用户只需要点击返回按钮就可以返回到上一层,对于开发人员来说,这个功能浏览器和手机都已经提供了,只需要调用一个 back 就可以。如果让我们来实现,就可以使用命令模式来将 back 映射到撤销功能。
代码语言:javascript复制/**
* 在浏览器中是URL跳转
* 在手机中是 Activity 跳转
*/
public interface Command<T> {
void execute(T param);
void back();
}
public class UrlCommand implements Command<String> {
private WindowHanlder windowHandler;
private Window preWindow;
private UrlReceiver receiver;
public UrlCommand(UrlReceiver rec) {
this.receiver = rec;
}
public void execute(String url) {
// 保存当前窗口
this.preWindow = windowHandler.getWindow();
rec.parseAndRend(url);
}
public void back() {
// 将原窗口再设置回去
windowHandler.setWindow(preWindow);
}
}
/**
* url 具体执行
*/
public class UrlReceiver {
public void parseAndRend(String url) {
// 获取url 对应内容
String content = parse(url);
// 渲染到窗口中
rend(content);
}
private void rend(String content) {
// 渲染窗口
}
}
public class User {
public static void main(String[] args) {
// 在浏览器里用户点击 url
String url = "";
UrlCommand command = new UrlCommand(new UrlReceiver());
command.execute(url);
// 用户点击返回
command.back();
}
}
此处 Command 只能支持单个命令的执行撤销,我们可以写一个 宏命令来封装一串命令执行后的撤销功能
代码语言:javascript复制public class MacroUrlCommand implements Command<String> {
private Stack<Command> commands = new Stack<>();
private UrlReceiver receiver;
public MacroUrlCommand(UrlReceiver rec) {
this.receiver = rec;
}
public void execute(String url) {
UrlCommand command = new UrlCommand(receiver);
commands.push(command);
command.execute(url);
}
public void back() {
if(CollectionUtils.isEmpty(commands)) {
return;
}
Command last = commands.pop();
last.back();
}
}
使用场景
- 请求方与接收方解耦,请求方不需要知道接收方的细节,就像我们使用遥控器调节电视,不需要知道原理,只需要按一下按钮
- 统一用户界面。开关电灯和开关电脑可以使用相同的用户界面,用户只需要按一下按钮就可以打开电灯或电脑,而不需要重新学习。命令模式通过抽象请求,来实现统一的命令界面
- 有撤销等功能要求的时候。命令模式定义中就含有此类功能,如果用户要求支持撤销,命令模式是天然的选择
- 需要支持 redo 和 undo。 数据库等软件在执行插入删除等命令前会将命令记录下来,以便系统崩溃时可以重建。也可以在执行命令后撤销对系统对修改
优点
- 请求者与实现者解耦,两个可以独立变化
- 命令可以结合起来变成组合命令(类似装饰模式)
- 命令作为一个执行单元,支持 redo 和 undo(为避免命令过重,可以与备忘录模式结合)
- 简单的批处理。可以对系统发一系列命令,系统顺序处理,降低通信成本
缺点
- 具体命令很容易膨胀,导致系统越来越难以维护
- 命令对原本直接调用加了一个间接层,增加了系统复杂度,使得理解系统更加困难
模式辨析
Callback 模式:告知某人操作已经完成,操作结果是什么
观察者模式:通知 n(n>=0) 个观察者某个事件发生
命令模式: 封装对对象的调用,使得命令有一个统一界面,相同命令可以相互转换