1.命令模式基本介绍
1)在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
2)命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
3)在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
4)通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
2.命令模式原理类图
对原理类图的说明-即(命名模式的角色及职责)
- Invoker:是调用者角色,负责对请求进行初始化,其中必须包含一个成员变量来存储对于命令对象的引用。发送者触发命令,而不向接收者直接发送请求。注意,发送者并不负责创建命令对象:它通常会通过构造函数从客户端处获得预先生成的命令:
- Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
- Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
- ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute
- Client:客户端,会创建并配置具体命令对象。客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函。此后,生成的命令就可以与一个或多个发送者相关联了。
3.
Command:命令角色
//命令接口 public interface Command { // 执行具体的命令 public void execute(); }
reciver:接收者角色
public class ApplicationServer { //定义接收者角色的服务器IP地址属性 private String ip; public ApplicationServer(String ip) { this.ip = ip; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } //接收者执行的具体命令 public void dump(){ System.out.println("[" + ip +"]mysqldump -uroot -p --all-databases > /backup/mysqldump/all.db "); } public void syncTime(){ System.out.println("[" + ip +"]ntpdate 0.asia.pool.ntp.org"); } public void shutdown(){ System.out.println("[" + ip +"]showdown now"); } }
具体命令:备份数据库
public class DumpCommand implements Command{ List<ApplicationServer> instanceList = new ArrayList(); // 通过构造方法参数传入 reciver public DumpCommand(ApplicationServer instance){ instanceList.add(instance); } // 通过构造方法参数传入 列表形式的 reciver public DumpCommand(List<ApplicationServer> instances){ this.instanceList = instances; } @Override public void execute() { // 将外侧传入的应用服务器进行遍历,并下达具体的命令给服务器 for (ApplicationServer instance : instanceList){ System.out.println("[" + instance.getIp() +"]正在执⾏备份数据库命令DUMP"); instance.dump(); } System.out.println("======================"); } }
具体命令:时钟校准
public class SyncTimeCommand implements Command{ List<ApplicationServer> instanceList = new ArrayList(); public SyncTimeCommand(ApplicationServer instance){ instanceList.add(instance); } public SyncTimeCommand(List<ApplicationServer> instances){ this.instanceList = instances; } @Override public void execute() { for (ApplicationServer instance : instanceList){ System.out.println("[" + instance.getIp() +"]正在执⾏时钟校准SYNC TIME"); instance.syncTime(); } System.out.println("======================"); } }
具体命令:关机
public class ShutdownCommand implements Command{ List<ApplicationServer> instanceList = new ArrayList(); public ShutdownCommand(ApplicationServer instance){ instanceList.add(instance); } public ShutdownCommand(List<ApplicationServer> instances){ this.instanceList = instances; } @Override public void execute() { for (ApplicationServer instance : instanceList){ System.out.println("[" + instance.getIp() +"]正在执⾏关机命令SHUTDOWN"); instance.shutdown(); } System.out.println("======================"); } }
invoker:命令的发送者
public class ManageServer { // 将外侧封装好的命令,依次放入命令列表中 List<Command> commandList = new ArrayList<>(); public void addCommand(Command command) { commandList.add(command); } //执行命令列表中的命令 public void execute(){ for(Command command : commandList){ command.execute(); } } }
Client客户端:
public class Client { public static void main(String[] args) { // 将所有接收者角色添加到列表中 List<ApplicationServer> instanceList = new ArrayList<>(); instanceList.add(new ApplicationServer("192.168.31.223")); instanceList.add(new ApplicationServer("192.168.31.203")); instanceList.add(new ApplicationServer("192.168.31.126")); instanceList.add(new ApplicationServer("192.168.31.183")); // 调用者角色:添加要执行的命令 ManageServer shellExecutor = new ManageServer(); shellExecutor.addCommand(new DumpCommand(instanceList)); shellExecutor.addCommand(new SyncTimeCommand(instanceList)); shellExecutor.addCommand(new ShutdownCommand(instanceList)); // 完成最终命令的执行 shellExecutor.execute(); } }
运行结果:
[192.168.31.223]正在执⾏备份数据库命令DUMP [192.168.31.223]mysqldump -uroot -p --all-databases > /backup/mysqldump/all.db [192.168.31.203]正在执⾏备份数据库命令DUMP [192.168.31.203]mysqldump -uroot -p --all-databases > /backup/mysqldump/all.db [192.168.31.126]正在执⾏备份数据库命令DUMP [192.168.31.126]mysqldump -uroot -p --all-databases > /backup/mysqldump/all.db [192.168.31.183]正在执⾏备份数据库命令DUMP [192.168.31.183]mysqldump -uroot -p --all-databases > /backup/mysqldump/all.db ====================== [192.168.31.223]正在执⾏时钟校准SYNC TIME [192.168.31.223]ntpdate 0.asia.pool.ntp.org [192.168.31.203]正在执⾏时钟校准SYNC TIME [192.168.31.203]ntpdate 0.asia.pool.ntp.org [192.168.31.126]正在执⾏时钟校准SYNC TIME [192.168.31.126]ntpdate 0.asia.pool.ntp.org [192.168.31.183]正在执⾏时钟校准SYNC TIME [192.168.31.183]ntpdate 0.asia.pool.ntp.org ====================== [192.168.31.223]正在执⾏关机命令SHUTDOWN [192.168.31.223]showdown now [192.168.31.203]正在执⾏关机命令SHUTDOWN [192.168.31.203]showdown now [192.168.31.126]正在执⾏关机命令SHUTDOWN [192.168.31.126]showdown now [192.168.31.183]正在执⾏关机命令SHUTDOWN [192.168.31.183]showdown now ======================
4.命令模式适合应⽤场景
1、如果你需要通过操作来参数化对象, 可使⽤命令模式。
命令模式可将特定的⽅法调⽤转化为独⽴对象。 这⼀改变也带来了许多有趣的应⽤: 你可以将命令作为⽅法的参数进⾏传递、 将命令保存在其他对象中, 或者在运⾏时切换已连接的命令等。
shellExecutor.addCommand(new DumpCommand(instanceList));
2、如果你想要将操作放⼊队列中、 操作的执⾏或者远程执⾏操作, 可使⽤命令模式。
同其他对象⼀样, 命令也可以实现序列化 (序列化的意思是转化为字符串), 从⽽能⽅便地写⼊⽂件或数据库中。 ⼀段时间后, 该字符串可被恢复成为最初的命令对象。 因此, 你可以延迟或计划命令的执⾏。 但其功能远不⽌如此! 使⽤同样的⽅式, 你还可以将命令放⼊队列、 记录命令或者通过⽹络发送命令。
ManageServer shellExecutor = new ManageServer(); shellExecutor.addCommand(new DumpCommand(instanceList)); shellExecutor.addCommand(new SyncTimeCommand(instanceList)); shellExecutor.addCommand(new ShutdownCommand(instanceList));
4.1.命令模式优点
单⼀职责原则。 你可以解耦触发和执⾏操作的类。
开闭原则。 你可以在不修改已有客户端代码的情况下在程序中创建新的命令。
你可以将⼀组简单命令组合成⼀个复杂命令。
4.2.命令模式缺点
代码可能会变得更加复杂, 因为你在发送者和接收者之间增加了⼀个全新的层次。