行为模式

定义:
把一个请求或者操作封装在命令对象中。
 
invoker  - command - receiver
invoker持有一个或多个具体的command, 每个command,有自己特定的receiver,invoker和receiver是解耦的
 
命令模式允许:
  1. 系统使用不同的请求把客户端参数化
  2. 对请求排队或者记录请求日志
  3. 可以提供命令的撤销和恢复功能
命令模式的结构
        顾名思义,命令模式就是对命令的封装,首先来看一下命令模式类图中的基本结构:
  • 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的松耦合。
 
命令模式的通用代码如下:
 
 
  1. class Invoker {  
  2.     private Command command;  
  3.     public void setCommand(Command command) {  
  4.         this.command = command;  
  5.     }  
  6.     public void action(){  
  7.         this.command.execute();  
  8.     }  
  9. }  
  10.   
  11. abstract class Command {  
  12.     public abstract void execute();  
  13. }  
  14.   
  15. class ConcreteCommand extends Command {  
  16.     private Receiver receiver;  
  17.     public ConcreteCommand(Receiver receiver){  
  18.         this.receiver = receiver;  
  19.     }  
  20.     public void execute() {  
  21.         this.receiver.doSomething();  
  22.     }  
  23. }  
  24.   
  25. class Receiver {  
  26.     public void doSomething(){  
  27.         System.out.println("接受者-业务逻辑处理");  
  28.     }  
  29. }  
  30.   
  31. public class Client {  
  32.     public static void main(String[] args){  
  33.         Receiver receiver = new Receiver();  
  34.         Command command = new ConcreteCommand(receiver);  
  35.         //客户端直接执行具体命令方式(此方式与类图相符)  
  36.         command.execute();  
  37.   
  38.         //客户端通过调用者来执行命令  
  39.         Invoker invoker = new Invoker();  
  40.         invoker.setCommand(command);  
  41.         invoker.action();  
  42.     }  
  43. }  
 
命令模式要点:
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),它体现了调用者的便利,可以做各种控制,是不是很赞
 
原文地址:https://www.cnblogs.com/43726581Gavin/p/9048498.html