设计模式之命令模式

命令模式(Command Pattern),给大家的第一感觉,就是给程序发送命令,比如:启动、暂停,然后程序根据接收到的命令直接执行就行。
这样的理解相对来说比较狭义,来看下命令模式官方的定义:
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
一个太狭义,一个又太晦涩。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )
我对命令模式的理解是这样的:我们将请求参数,以及请求的执行逻辑、依赖对象等,封装在一个对象中,将这个对象推送到执行的引擎中,由执行引擎来驱动执行。我们通过命令模式可以更好的封装逻辑、管理逻辑。

它是面向对象的23种设计模式中的一种,属于行为模式的范围。
我们来看这样的一个例子:

工作接口类

复制代码
 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 /**
 4  * @discription
 5  */
 6 public interface IWorkCommand {
 7 
 8     String getWorkName();
 9 
10     void execute();
11 }

司机工作类

复制代码
 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 import lombok.AllArgsConstructor;
 4 import lombok.extern.slf4j.Slf4j;
 5 
 6 /**
 7  * @discription
 8  */
 9 @AllArgsConstructor
10 @Slf4j
11 public class DriverWork implements IWorkCommand {
12 
13     private String name;
14 
15     @Override
16     public String getWorkName() {
17         return "Driver: " + name;
18     }
19 
20     @Override
21     public void execute() {
22         log.warn("start invoke {} work", getWorkName());
23         log.warn("运送货物到指定目的地");
24         log.warn("打扫汽车卫生");
25         log.warn("将汽车送回停车地点");
26     }
27 }

程序员工作类

复制代码
 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 import lombok.AllArgsConstructor;
 4 import lombok.extern.slf4j.Slf4j;
 5 
 6 /**
 7  * @discription
 8  */
 9 
10 @AllArgsConstructor
11 @Slf4j
12 public class ProgrammerWork implements IWorkCommand{
13 
14     private String name;
15     @Override
16     public String getWorkName() {
17         return "programmer: "+name;
18     }
19 
20     @Override
21     public void execute() {
22         log.warn("start invoke {} work", getWorkName());
23         log.warn("修复昨天遗留的问题.");
24         log.warn("完成今天的开发工作.");
25         log.warn("优化系统性能和稳定性.");
26     }
27 }

执行中心

复制代码
 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 /**
 4  * @discription
 5  */
 6 public class InvokeCenter {
 7     public void invokeWork(IWorkCommand workCommand) {
 8 
 9         workCommand.execute();
10     }
11 }

主类

复制代码
 1 package com.example.demo.learn.pattern.behavior.command;
 2 
 3 /**
 4  * @discription
 5  */
 6 public class PatternMain {
 7     public static void main(String[] args) {
 8         IWorkCommand programmerWork = new ProgrammerWork("小p");
 9         IWorkCommand driverWork = new DriverWork("小d");
10         InvokeCenter invokeCenter = new InvokeCenter();
11         invokeCenter.invokeWork(driverWork);
12         invokeCenter.invokeWork(programmerWork);
13     }
14 }

输出结果是这样的:

复制代码
Connected to the target VM, address: '127.0.0.1:52437', transport: 'socket'
15:57:07.856 [main] WARN com.example.demo.learn.pattern.behavior.command.DriverWork - start invoke Driver: 小d work
15:57:07.866 [main] WARN com.example.demo.learn.pattern.behavior.command.DriverWork - 运送货物到指定目的地
15:57:07.866 [main] WARN com.example.demo.learn.pattern.behavior.command.DriverWork - 打扫汽车卫生
15:57:07.866 [main] WARN com.example.demo.learn.pattern.behavior.command.DriverWork - 将汽车送回停车地点
15:57:07.867 [main] WARN com.example.demo.learn.pattern.behavior.command.ProgrammerWork - start invoke programmer: 小p work
15:57:07.867 [main] WARN com.example.demo.learn.pattern.behavior.command.ProgrammerWork - 修复昨天遗留的问题.
15:57:07.868 [main] WARN com.example.demo.learn.pattern.behavior.command.ProgrammerWork - 完成今天的开发工作.
15:57:07.868 [main] WARN com.example.demo.learn.pattern.behavior.command.ProgrammerWork - 优化系统性能和稳定性.
Disconnected from the target VM, address: '127.0.0.1:52437', transport: 'socket'

Process finished with exit code 0

在这个例子中,我们并不直接执行各种具体的工作,而是将他们都封装到一段方法中,由执行引擎统一的来执行。
这段逻辑是不是和通过实例化Thread 的方式,进行多线程操作的逻辑很像?(请参考这篇文章)
没错,在多线程的执行中,就应用到了命令模式。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )
我们结合命令模式,可以发现这种结构大概有这3个角色:
1、一个抽象的命令接口(抽象类),在这里是 ,他用来约定我们要执行的方法放在哪里,怎么执行。我们一般称之为抽象命令类 Command
2、实现了抽象命令的实现类,在这里是,我们一般通过编写这个类,来实现我们想要的逻辑。我们一般称之为具体命令类 Concrete Command
3、调用者,在这里是,我们一般通过调用者来存储和执行命令,一般也称之为 请求者/调用者 invoker
除此之外还有一个角色
实现者(Receiver),这个角色,在我们的示例中隐藏的有点深,是log对象,也就是命令对象中,真正执行逻辑操作的对象。
注意调用者invoker 是不直接持有实现者的,两者是没有耦合关系的,是通过持有命令对象,间接的持有了调用者,间接的驱动了调用者。这样做既可以让调用者不关心具体的业务(譬如说线程池从来不直接持有执行对象的引用,而只持有对应的执行方法 (run()),由执行方法来组织逻辑和解耦)。
类图大概是这样子:

有些人问,我直接依赖调用者,然后调用调用者的某些方法,来实现我需要的逻辑是否可以,

答案是可以,但是如果第三方想要执行你的这段逻辑、或者你需要将这段逻辑交给第三方去在特定的时机处理执行,你会怎么做呢?
这时候你就需要将你实现的这段逻辑封装到一个对象中,交给其他人,这时候最终又变成了命令模式的体现。

命令模式除了进行解耦,还有一个好处就是可以编排和管理业务逻辑(命令)。
举个例子:有时候我们要做的一个业务包含几件相关的事,事情之间没有先后顺序,
比如我们去超市买一瓶可乐,需要做:
1、拿可乐
2、支付
此时我们就可以将每件事各自封装成一个命令,将整个业务包含的命令,打包丢给执行引擎,这样是不是就很好处理业务了。
A业务要做:a,b,c 三个命令
B业务要做:a,c,d,e 四个命令
我们只要定义每个命令,然后封装好每块业务需要做的几件事(命令),这样面相对象的设计感觉一下子就出来了。