定义
命令模式也称作动作模式,属于行为型模式的一种,书中的定义:将一个请求封装为一个对象,从而使得可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销操作。
本质上看,就是将要执行的任务参数化
具体做法就是将命令封装为参数,并且由命令去执行底层真实的业务逻辑。客户端需要做的事情是,创建命令,将命令发送至调用者,再由调用者去执行命令。
可以理解为增加了一个中间层,这个中间层就是命令参数,实现了调用者和执行者之间的解耦,换句话说,执行者不再需要调用者直接去调用执行具体的业务逻辑,而是通过命令的方式告诉执行者,具体需要执行的任务。而在这个命令执行过程中,自然就有了更多的机会去控制不同命令的执行一些额外的操作,比如定义中说的对请求排队,记录日志,撤销等操作。
以一个餐厅示例说明,张三带着别人的女朋友去吃印度特色美食,如果没有使用命令模式的效果就是,张三直接向厨房的厨师点餐,厨师是执行者,张三向厨师菜单,厨师制作。假设客流量比较大,菜品比较多,厨师就要一边制作菜品,一边忙着管理菜单。如果使用命令模式就相当于增加了服务员或者点餐台这个角色,张三向点菜台点餐,具体后续的操作由服务员去向厨师发送命令。而在这个过程中,就可以进行一些额外的操作,比如对按照先来后进行排序,或者某个菜品售罄的情况下可以直接告知张三,并且也可以对一天的营业额,客流量、利润等等信息进行记录统计。
再比如,在操作系统的世界中,上层应用中直接调用内核接口是绝对不被允许的,于是就搞了一个外壳,通过这个外壳shell去调用,内核的任务很单一,仅仅是按照自己的执行逻辑去执行任务,具体内核是怎么操作的,上层用户无需知道,在这个过程中也可以进行各种其他的操作,比如权限的验证,操作日志记录,拦截非法操作等。
命令模式中总体其实就分为三种角色:命令接收者、命令调用者以及命令,过程就是命令调用者享命令接收者发送命令
- 抽象命令角色(Command):命令的抽象,负责当前的命令的调用,比如包含一个execute方法
- 具体命令角色(Concrete command):命令的具体实现类,应该持有命令接收者,将命令发送至接收者
- 接收者角色(Ceceiver):接收者,负责接收命令,进行具体的执行逻辑,注意,这里是接收者,而不是执行者,具体的执行逻辑实现是自由的
- 调用者角色(Invoker):持有命令,负责调用命令,比如调用execute方法
UML:
图片来源:百度图库
2.示例
以一个服务器执行操作指令为示例,对命令模式进行一个基本实现,还是一个观点,设计模式只是思想,以下示例只是一个非常简单的例子,目的仅仅是对思想进行说明,具体实现应充分考虑实际情况,做出更优的设计。
定义抽象命令角色,接口或抽象类,命令角色应该持有接收者的实例,并包含一个实际的执行命令的方法
/**
* @description: 命令抽象
* @version: 1.0
*/
public abstract class Command {
protected List<Server> servers;
public Command(List<Server> servers) {
this.servers = servers;
}
/**
* 命令执行方法
*/
abstract void execute();
}
具体的命令实现类,以下实现了三个命令参数,模拟查看进程命令、 统计网络状态命令、以及关闭机器命令
PSCommand:
/**
* @description: 查看系统进程命令
* @version: 1.0
*/
public class PSCommand extends Command{
public PSCommand(List<Server> servers) {
super(servers);
}
@Override
public void execute() {
for (Server server : servers){
System.out.println("检查系统状态,查询系统进程");
server.processShow();
}
}
}
NetCommand :
/**
* @description: 统计网络状态命令
* @version: 1.0
*/
public class NetCommand extends Command{
public NetCommand(List<Server> servers) {
super(servers);
}
@Override
public void execute() {
for (Server server : servers){
System.out.println("检查系统状态,查询网络状态");
server.netStat();
}
}
}
ShutdownCommand :
/**
* @description: 关机指令
* @version: 1.0
*/
public class ShutdownCommand extends Command{
public ShutdownCommand(List<Server> servers) {
super(servers);
}
@Override
public void execute() {
for (Server server : servers){
System.out.println("检查系统状态,准备关闭机器");
server.shutdown();;
}
}
}
接下来是实现命令接收者,在这个示例中大概就是服务器实例,命令接收者的工作是接收具体的命令,示例中体现为等待命令的主动调用,当一个命令执行就会调用命令对应的接收方法,而接收者应该对命令参数对应的操作进行具体的实现,以上的实现方式当然是自由的,只要遵循命令模式的思想即可。
这里顺便对做了一个抽象,以便可以扩展其他类型的服务器实例。
Server :
/**
* @description: 服务器 (命令接收者)
* @version: 1.0
*/
public abstract class Server {
protected String ip;
public Server(String ip) {
this.ip = ip;
}
public abstract void processShow();
public abstract void netStat();
public abstract void shutdown();
}
ApplicationServer :
/**
* @description: 服务器(命令接受者)
* @version: 1.0
*/
public class ApplicationServer extends Server{
public ApplicationServer(String ip) {
super(ip);
}
@Override
public void processShow() {
System.out.println(this.ip + "=>查看系统进程");
}
@Override
public void netStat() {
System.out.println(this.ip + "=>查看网络状态");
}
@Override
public void shutdown() {
System.out.println(this.ip + "=>执行关机操作");
}
}
剩下的就是命令的调用者,命令的调用者主要职责就是创建命令,调用命令,命令调用者应持有命令实例,示例中体现为在调用者中维护一个命令数组,并对外提供构造的方式初始化命令。除此之外,该角色应该实现具体的命令调用逻辑,实例中只是简单的遍历命令数组,执行命令的execute方法。实际业务中实现依旧是自由的,只要遵循思想本质即可。
/**
* @description: 命令调用者 invoke
* @version: 1.0
*/
public class ServerInvoker {
List<Command> commands = new ArrayList<>();
public void addCommand(Command command){
commands.add(command);
}
//调用命令执行
public void invoke(){
for (Command command : commands){
command.execute();
}
}
}
测试:
/**
* @description: test
* @version: 1.0
*/
public class Test {
public static void main(String[] args) {
//创建server实例
List<Server> servers = new ArrayList<>();
servers.add(new ApplicationServer("192.168.1.13"));
servers.add(new ApplicationServer("192.168.1.14"));
servers.add(new ApplicationServer("192.168.1.15"));
//调用者 & 添加命令
ServerInvoker invoker = new ServerInvoker();
invoker.addCommand(new PSCommand(servers));
invoker.addCommand(new NetCommand(servers));
invoker.addCommand(new ShutdownCommand(servers));
//执行命令
invoker.invoke();
}
}
input:
检查系统状态,查询系统进程
192.168.1.13=>查看系统进程
检查系统状态,查询系统进程
192.168.1.14=>查看系统进程
检查系统状态,查询系统进程
192.168.1.15=>查看系统进程
检查系统状态,查询网络状态
192.168.1.13=>查看网络状态
检查系统状态,查询网络状态
192.168.1.14=>查看网络状态
检查系统状态,查询网络状态
192.168.1.15=>查看网络状态
检查系统状态,准备关闭机器
192.168.1.13=>执行关机操作
检查系统状态,准备关闭机器
192.168.1.14=>执行关机操作
检查系统状态,准备关闭机器
192.168.1.15=>执行关机操作
3.总结
一句话概括命令模式就是将命令调用者和接收者分离,调用者只需要调用命令,不关心是谁接收,怎么执行,而接收者只是安静的等待命令的到来,命令是谁发的,何时发的,怎么发的,接收者都不知道,双方不需要感知对方的存在,他们之间的纽带就是命令。调用者只需要知道“我要执行什么命令”,而接收者只知道对于一个命令“我要干什么”,命令就是他们之间的“请求参数”。