定义:
把一个请求或者操作封装在命令对象中。
invoker - command - receiver
invoker持有一个或多个具体的command, 每个command,有自己特定的receiver,invoker和receiver是解耦的
命令模式允许:
- 系统使用不同的请求把客户端参数化
- 对请求排队或者记录请求日志
- 可以提供命令的撤销和恢复功能
命令模式的结构
顾名思义,命令模式就是对命令的封装,首先来看一下命令模式类图中的基本结构:
- Command类:是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个execute方法用来执行命令。
-
ConcreteCommand类:Command类的实现类,对抽象类中声明的方法进行实现。由三个要素组成:执行者,执行者要作的操作和被执行的对象组成。
- Client类:最终的客户端调用类。
以上三个类的作用应该是比较好理解的,下面我们重点说一下Invoker类和Recevier类。
-
Invoker类:调用者,负责调用命令。生成有序的命令队列按顺序执行命令操作提供撤销命令操作记录已经操作的命令
- Receiver类:接收者,负责接收命令并且执行命令,真正执行逻辑操作的对象。
所谓对命令的封装,说白了,无非就是把一系列的操作写到一个方法中,然后供客户端调用就行了,反映到类图上,只需要一个ConcreteCommand类和Client类就可以完成对命令的封装,即使再进一步,为了增加灵活性,可以再增加一个Command类进行适当地抽象,这个调用者和接收者到底是什么作用呢?
其实大家可以换一个角度去想:假如仅仅是简单地把一些操作封装起来作为一条命令供别人调用,怎么能称为一种模式呢?命令模式作为一种行为类模式,首先要做到低耦合,耦合度低了才能提高灵活性,而加入调用者和接收者两个角色的目的也正是为此。
为了实现松耦合,我们现在来想一下,周末去请朋友吃饭,服务员mm问你吃什么,你说水煮活鱼,然后在菜单上面,写上水煮活鱼。下个星期天想吃花生米啤酒,同样也写在订单上,然后服务员mm把订单拿给厨师。
在上面的例子中,无论你点了什么菜,服务员mm,只需要知道顾客点的什么菜,从而给不同的厨师做(虽然不是直接的,但最终凉菜会给凉菜的师傅做,热菜的会给热菜的厨师做),然而具体的怎么做是厨师的事情,服务员知道顾客点什么菜,只是为了能正确的交给厨师去做。
其实在这个例子中,也是一个命令模式的例子,不同的订单对应的有不同的厨师,最终订单拿到厨师面前,就是对厨师下了个命令,要做菜了。每订单或者说是每种菜都对应着自己的厨师,所以,客服需要有订单的的引用,订单为了知道调用哪个厨师,需要有厨师引用;两个引用都是使用的组合。从而可以调用多种自己需要对象的方法来实现松耦合。这里记住:客服mm知道顾客点菜的目的是为了让不同的厨师去做。再直接一些就是为了使用不同的命令。
分析一下角色:服务员MM相当于一个invoker,负责调用做菜的命令,我们客户是client,与服务员MM联系,下单是一个命令command,点九转大肠是一个具体的命令concreteCommand,点夫妻肺片也是一个具体的命令concreteCommand,每个命令指定一个receiver来接收,这个reciever就是厨师,九转大肠交给鲁菜的厨师来做,夫妻肺片交给川菜的厨师来做。
命令模式主要通过中介Command实现了发出命令者和命令的执行者,也即Invoke类和Receiver的松耦合。
命令模式的通用代码如下:
[java] view plaincopy
-
class Invoker {
-
private Command command;
-
public void setCommand(Command command) {
-
this.command = command;
- }
-
public void action(){
-
this.command.execute();
- }
- }
-
abstract class Command {
-
public abstract void execute();
- }
-
class ConcreteCommand extends Command {
-
private Receiver receiver;
-
public ConcreteCommand(Receiver receiver){
-
this.receiver = receiver;
- }
-
public void execute() {
-
this.receiver.doSomething();
- }
- }
-
class Receiver {
-
public void doSomething(){
-
System.out.println("接受者-业务逻辑处理");
- }
- }
-
public class Client {
-
public static void main(String[] args){
-
Receiver receiver = new Receiver();
-
Command command = new ConcreteCommand(receiver);
-
//客户端直接执行具体命令方式(此方式与类图相符)
- command.execute();
-
//客户端通过调用者来执行命令
-
Invoker invoker = new Invoker();
- invoker.setCommand(command);
- invoker.action();
- }
- }
命令模式要点:
1.Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。
2.实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。
3.使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。
适用性:
在下面的情况下应当考虑使用命令模式:
1.使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。
2.需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
优点:
解耦了发送者和接受者之间联系。 发送者调用一个操作,接受者接受请求执行相应的动作,因为使用Command模式解耦,发送者无需知道接受者任何接口。
不少Command模式的代码都是针对图形界面的,它实际就是菜单命令,我们在一个下拉菜单选择一个命令时,然后会执行一些动作.
将这些命令封装成在一个类中,然后用户(调用者)再对这个类进行操作,这就是Command模式,换句话说,本来用户(调用者)是直接调用这些命令的,如菜单上打开文档(调用者),就直接指向打开文档的代码,使用Command模式,就是在这两者之间增加一个中间者,将这种直接关系拗断,同时两者之间都隔离,基本没有关系了.
显然这样做的好处是符合封装的特性,降低耦合度,Command是将对行为进行封装的典型模式,Factory是将创建进行封装的模式,
所以从这个角度来看,考试题2使用命令模式应该更好一些。
特点:
1》 分布登记统一执行:
在作程序时,经常碰到一些需求,先注册一些操作,并不马上执行,等最终确定后统一执行。如一个具体的例子:用户定制自己的报表,可以订阅饼,柱,折线,曲线图,客户选择相应的报表组合,这样对应一个命令集合,在没确定之前用户可以增删这些报表(命令),等最终确定统一交给调用者根据命令执行,生成组合报表。实现了命令分布提出,确定后统一执行的功能。
2》形如流水线操作:
这样就给了我们一种系统设计的框架,
模型+工具+命令
客户端产生命令,命令调用工具操作模型。
3》系统需要支持命令的撤消(undo)。提供redo()方法
我们可以和容易的加入undo和redo,这个不难理解
4》在Invoker中我们可以实现跟踪,和日志。
5》当系统需要为某项复制增加形的功能的时候,命令模式使新的功能(表现为一种命令)很容易地被加入到服务种里。
命令联系了工具类即执行类和系统逻辑
变种:
1》去掉 调用者
产生命令集合后,我们可以直接在client中迭代执行执行操作
2》 变化 调用者 成为 跟踪者
3》去掉 命令 用map替代
4》去掉执行者:
直接在命令中(execute方法中)加业务逻辑。这样只适合于简单的小的系统.
而且这就类似于策略模式了
当receiver无法抽象出来的时候,可以通过抽象的command作为中间层。
两方面理解
解耦:
这个模式特别像 策略+代理,具体的Command作为Receiver的代理,只是Command没有和Receiver实现同一个接口,其实更准确的说,ConcreteCommand是对Receiver的一层封装。而Invoker持有Command类型的对象,这正像策略模式那样,交由上层client来决定执行哪个命令。
我把这种模式理解为强行策略,假设底层的策略无法抽象出统一的接口,那么可以用一个command类,作为统一的接口,而具体的执行类作为接口实现类的一个成员变量,那么就可以利用策略来解决这个问题了。是不是!!假设有3个receiver,receiverA提供method1方法,receiverB提供method1方法,receiverC提供method1方法,他们的入参和返回都是相同的,这显然用策略就能解决了。但是事实上,每个receiver提供的method的入参和返回值不同,这就不能抽象出接口,而假设具体的command是相同的,可以处理入参和返回值,以达到统一,这似乎有点像适配的功能了。所以这个例子不是很好。再想一个,recevierA.method1,recevierA.method2,receiverB.method3,这样,receiverA和receiverB就不能实现同一个接口,这样是没法用策略模式的,那么我可以用command做一层封装,对于每个method都有一个具体的command,实现了策略的目的。
异步:
client调用invoker执行命令,而invoker调用command执行命令,这个command是一个具体的对象,把这个command交给其他的线程,其他的线程调用receiver来异步执行,调用方不关心结果,这是命令模式提供的另一种功能
调用者的便利:
调用者可以做轨迹、可以undo,可以做队列,,在这一层来做是最合适的
举个jdk里经典的应用命令模式的场景,Runnable,实际上Runnable就是一个Command,而Runnable的实现可以持有一个recevier来做具体的执行,这时候invoker就是ExecutorService(更深了说是Worker),它体现了调用者的便利,可以做各种控制,是不是很赞