什么是命令模式(Command)
概念
命令模式(Command Pattern)属于行为型模式,定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。命令模式讲究的是解耦合,可延迟,像线程就是命令模式的典例,如果不适用命令模式的话,那么就无法将我们要执行的方法交给线程去执行,必须立马执行方法。
举个例子,我们去餐厅吃饭,服务员都会拿个菜单给我们,这个菜单就是已经写好的“命令”,只需要我们跟服务员(请求者)说想要吃什么菜(命令),那服务员就会通知厨师(实现者)去做什么菜,我们觉得菜够了,也可以取消没有做的菜;还有电视机的遥控器,当我们想换台时,就拿起遥控器(请求者),按下其他台的按钮(命令),电视机(实现者)就会换台了,我们想退回去看直接点返回即可。
其实我也一直在想,这两个例子中“我们”是处于一个什么样的角色,“我们”是处于命令模式这个模块之外的角色,我们想实现需求就得去调用请求者,像点菜,你直接跟厨师说,厨师哪里会理你,还有你直接对电视机喊也没用。这里我们也可以体会到请求者是对命令如何使用做了封装,我们想使用只需要和请求者交互即可。
命令模式也非常像我们打开Windows或Linux的命令操作界面,我们只需要输入命令和参数,就可以执行功能了。
优点
- 降低系统的耦合度。通过引入命令这类对象,降低了请求者和实现者的耦合。
- 符合开放封闭原则。想要新增操作,只需要写新的命令类即可。
- 可以延迟执行、撤销,排队请求。
大部分的设计模式都是为了解耦合,高内聚,我写的都有点腻了。
缺点
- 增加了系统的复杂性。使用命令模式可能会使系统产生大量的命令类,每个操作都得去实现一个相应的命令类,大量的命令类会增加系统的复杂性。
- 降低了代码的可读性。使用命令模式是通过命令的方式把请求者和实现者分离开来,这不仅带来了代码设计上的困难,也增加了系统的理解难度。
大部分的设计模式都是存在一定的实现难度和增加系统的复杂性。
原则
“+”代表遵守,“-”代表不遵守或者不相关
原则 | 开放封闭 | 单一职责 | 迪米特 | 里氏替换 | 依赖倒置 | 接口隔离 | 合成复用 |
---|---|---|---|---|---|---|---|
+ | + | - | - | + | - | - | |
适用场景
- 基于事件驱动的模块。像浏览器的按钮、操作系统的控制台,都是一个动作或者一行命令触发就要去完成相应的操作。
- 要求请求者和实现者解耦。请求者和实现者不直接进行交互。
- 存在可以延迟执行、撤销、回滚等情况。像记录日志、保存信息到数据库这些。
如何实现
想要实现命令模式,需要以下四样东西:
- 抽象命令接口:用来声明命令,拥有执行命令的抽象方法 execute()。
- 具体命令类:实现抽象命令接口,并调用实现者完成功能。
- 实现者/接收者类:为具体命令类提供服务。
- 请求者/调用者类:命令的调用者,负责管理和执行命令。
上类图
这里我复制其他网站看到的命令模式的类图。大家可以看看了解一下,然后再去看我写的代码。
上例子
夏天来了,家家户户都装了空调,现有一家安装空调以及维修空调的公司,这公司的领导负责给工人安排安装装和维修空调工作,工人接收到工作后就去安装和维修。
类图:
代码:
/**
* Created on 2021/6/4.
*
* @author xuxiaobai
*/
public class CommandTest {
public static void main(String[] args) {
//领导
Leader leader=new Leader();
//只有张三一个工人
Worker worker = new Worker("张三");
System.out.println("领导安排工作");
leader.addPlan(new InstallWork("A镇58号", worker));
leader.addPlan(new RepairWork("B镇23号", worker));
//哎,领导取消安排了
leader.cancel();
//重新安排
leader.addPlan(new InstallWork("C镇15号", worker));
leader.addPlan(new InstallWork("D镇8号", worker));
leader.addPlan(new InstallWork("E镇6号", worker));
System.out.println("领导下达命令");
leader.order();
/**
* 结果:
* 领导安排工作
* 取消工作安排
* 领导下达命令
* 张三骑上了心爱的面包车,到达了C镇15号
* 张三开始工作了
* 卸下空调
* 装内机
* 装外机
* 检验
* 完成收工
* 张三骑上了心爱的面包车,到达了D镇8号
* 张三开始工作了
* 卸下空调
* 装内机
* 装外机
* 检验
* 完成收工
* 张三骑上了心爱的面包车,到达了E镇6号
* 张三开始工作了
* 卸下空调
* 装内机
* 装外机
* 检验
* 完成收工
*/
}
}
/**
* 实现者/接收者类
* 工人类
* 功能:到达目的地、装空调、修空调
*/
class Worker{
private String name;
//是否携带空调
private boolean airConditioner=false;
public Worker(String name){
this.name=name;
}
/**
* 到达目的地
* @param address
*/
public void arrive(String address){
System.out.print(name+"骑上了心爱的面包车,到达了");
System.out.println(address);
}
/**
* 带上空调
*/
public void getAirConditioner(){
this.airConditioner=true;
}
/**
* 放下空调
*/
private void putAirConditioner(){
this.airConditioner=false;
}
/**
* 装空调
*/
public void installAirConditioner(){
System.out.println(name+"开始工作了");
System.out.println("卸下空调");
putAirConditioner();
System.out.println("装内机");
System.out.println("装外机");
System.out.println("检验");
System.out.println("完成收工");
}
/**
* 修空调
*/
public void repairAirConditioner(){
System.out.println(name+"开始工作了");
System.out.println("拆外机壳");
System.out.println("检查");
System.out.println("拆内机壳");
System.out.println("检查");
System.out.println("维修");
System.out.println("完成收工");
}
}
/**
* 抽象命令接口
* 这里只能用接口,所以就用接口了,如果你想写个例子可以考虑写个抽象类
* 工作类
*/
interface Work{
void execute();
}
/**
* 具体命令类
* 装空调
*/
class InstallWork implements Work{
//地址
private String address;
//工人
private Worker worker;
//领空调凭证
private boolean voucher=false;
public InstallWork(String address,Worker worker){
this.address=address;
this.worker=worker;
this.voucher=true;
}
@Override
public void execute() {
if (voucher){
//有凭证才能取空调
worker.getAirConditioner();
}else {
System.out.println("没有取空调凭证,拿不到空调");
return;
}
worker.arrive(address);
worker.installAirConditioner();
}
}
/**
* 具体命令类
* 修空调
*/
class RepairWork implements Work{
//地址
private String address;
//工人
private Worker worker;
public RepairWork(String address,Worker worker){
this.address=address;
this.worker=worker;
}
@Override
public void execute() {
worker.arrive(address);
worker.repairAirConditioner();
}
}
/**
* 空调公司领导
*/
class Leader{
List<Work> works=new ArrayList<>();
/**
* 增加工作安排
* @param work
*/
public void addPlan(Work work){
works.add(work);
}
/**
* 下发工作
*/
public void order(){
Iterator<Work> iterator = works.iterator();
while (iterator.hasNext()){
Work work = iterator.next();
work.execute();
iterator.remove();
}
}
/**
* 取消安排
*/
public void cancel(){
System.out.println("取消工作安排");
works.clear();
}
}
大家简单看一下,理一下思路就可以了。
如果是不使用命令模式的话,那么就得我们直接操作Worker对象了,什么步骤都要我们来操作,而且不能够延时执行。使用了命令模式,我们甚至可以使用工厂来创建命令类,这样使用起来就更加地简便了。
总结
命令模式的关键在于它把请求/操作封装成一个命令对象,这些命令对象都是提前写好的,然后把这些命令交给实现者去完成操作,在命令没有执行之前可以撤销、新增命令。
我在学习命令模式的时候,就一直在想命令模式跟写方法有什么区别?直接写方法也是封装操作逻辑,但这样的话就不能延迟执行、撤销和回滚。