在前面加上

谈到命令,大部分的人脑海中会想到以下这幅画面

 

这在现实生活中是一副讽刺漫画,做决定的人不清楚运行决定的人有何特点,瞎指挥、外行领导内行说的就是这样的。只是在软件设计领域,我们显然要为这样的现象正名了,让狮王能记住全部属下的特点并直接打电话通知任务。显然是为难他了,领导们非常忙。这是秘书处的工作。狮王仅仅要做出指示“近期鼠患猖獗。该抓抓了”,那秘书们就要起草红头文件(命令)并发给相关运行部门(猫),各司其职提高效率,公布请求的(秘书处)和运行请求的(猫)分离开来,将行为(抓鼠)封装成对象(红头文件),这就是命令模式。

官方定义

将一个请求封装成对象,从而可用不同的请求对客户进行參数化,对请求排队或记录请求日志,以及运行可撤销的操作——GOF23。

将请求封装成对象,这个对象就是命令对象。在结构化程序中。请求一般是以函数的形式表现的。对于该请求中可能涉及到的运行对象,假设我们以函參的形式传递,这会造成下面几个问题

1)紧耦合。客户程序须要依赖运行对象,在上例中,上层在公布命令时须要依赖详细的属下,这会违反依赖倒置原则;把请求封装成命令对象,这些命令对象遵循共同的命令接口,这就攻克了高层依赖问题。

2)函数没用强调撤销(undo)操作,函数中对对象状态的保存须要额外的业务逻辑。

3)函数的复用性及扩展型较差,这也是为什么结构化逐渐被对象语言代替的原因。

不同的请求能够对客户进行參数化,这个类似于策略模式的动态设置算法。在上例中。不同的请求被封装成了不同的命令。用户是能够自由选择当前要运行的命令是哪个。

“面向接口而不是实现”的编程原则,能够在运行时依据上下文情况来选择详细命令。比方,狮王并不总和老鼠过不去,这段时间机关单位工作作风较差,迟到现象频发。狮王就会指示“多打鸣,抓四风”,这时秘书处就会起草打鸣文件并保证其能够下发运行。秘书处的职责,事实上就像是触发器invoker(遥控器),他们起草何种文件并运行,就相当于触发器绑定了何种命令对象。这样的绑定关系是由Client(狮王)决定的。

请求排队、记录日志、和可撤销,这三点是命令模式的典型应用,在后文会提及,这个能够从类似文本编辑软件word中做比較,这些软件的用户界面设计广泛借鉴了命令模式的特点。封装请求成命令对象后,能够把一系列的命令排队、记录、撤销等。

角色

在该模式中包括下面几个角色:

Command —— 命令接口,上例中,相应于秘书处的红头文件的模板。当中定义了详细命令所需实现的方法。如execute、undo等。

ConcreateCommand —— 详细命令,上例中,相应于抓鼠文件、打鸣文件。

这些命令中一般会包括接受者的引用,比方抓鼠红头文件会相应于一个猫的引用实例,execute方法会调用猫的捕鼠方法,打鸣同理。

Client —— 创建详细命令并设置接受者,这是命令的实际制定者。相应于狮王。每一个详细命令对象在创建时。其接受者就已经被定义好了,在创建详细命令时须要关心谁来运行。注意这里的Client并非通常所说的使用用户,Client的作用类似于装载器Loader,创建不同的命令对象并将其动态绑定在invoker中。

Invoker —— 要求命令运行的对象,相应于上例的秘书处,他们的任务是确保上通下达。对于每一个详细的命令都要保证被运行。

Receiver —— 接受者。命令的详细运行者,相应于上例的阿鸡阿猫,这些运行者一般会被组合在详细命令中。他们有自己的方法,这些方法一般会在详细命令的execute方法中被调用。

代码实现

实现的UML图例如以下所看到的

以上图为例,Command是命令接口。秘书类Secretary组合该接口对象,捕鼠和打鸣实现了该命令接口。因为逻辑比較简单,直接贴代码,首先是个简单到不好意思的Command接口。

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public interface Command {public abstract void execute();
}
</span></span></span>

详细命令包含两个,CatchMouseCommand和CrowCommand

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class CatchMouseCommand implements Command{Cat cat;public CatchMouseCommand(Cat cat) {// TODO Auto-generated constructor stubthis.cat = cat;}@Overridepublic void execute() {// TODO Auto-generated method stubcat.CatchMouse();}}</span></span></span>

And

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class CrowCommand implements Command{Cock cock;public CrowCommand(Cock cock) {// TODO Auto-generated constructor stubthis.cock = cock;}@Overridepublic void execute() {// TODO Auto-generated method stubcock.Crow();}}</span></span></span>

能够看到这两个详细命令类都组合了接受者类。Cat或Cock,简单起见,这两个接受这类仅仅含有一个相应方法,例如以下

Cat

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">//猫类-receiver
public class Cat {//捕鼠方法public void CatchMouse() {System.out.println("Cat is catching mouse");}
}</span></span></span>

Cock

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">//公鸡类-receiver
public class Cock {//打鸣方法public void Crow() {System.out.println("Cock is crowing");}
}</span></span></span>

秘书类里组合了命令对象和两个方法,setCommand是因为设置详细命令,而publicCommand则类似于结构化语言中的回调,当须要运行该命令时这种方法就会调用详细命令的execute函数,秘书类作为请求发起者,并不关心详细命令由谁运行,怎样运行,这就实现了请求者和实现者的解耦。

invoker不关心receiver,反之亦然。

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class Secretary {Command command;public void setCommand(Command command) {this.command = command;}public void publicCommand() {this.command.execute();}
}</span></span></span>

Lion在本例中是命令的其实的制定者。他创建了详细命令并在合适时机交由秘书完毕命令公布和运行(receiver完毕),软件开发实践中这个类更像是一个装载者,他建立一种映射关系,特定的invoker绑定特定的ConcreteCommand,后文会以word编辑器软件开发进行分析。

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">/*** Lion不是实际的使用客户,其作用相似于装载器Loader,其作用是* 为触发器Invoker(秘书对象secretary)绑定不同的命令对象(catchMouseCommand)。

* 这就像在相似word软件的菜单中,不同的菜单项MenuItem就是不同Invoker,每一个菜单项 * 都会绑定一个命令对象,甚至多个菜单项可能绑定同样的命令对象。 * 用户点击时菜单项会触发其绑定的命令对象方法。

*/ public class Lion { Secretary secretary; public Lion(Secretary secretary) { // TODO Auto-generated constructor stub this.secretary = secretary; } public void createCatchMouseOrder() { Cat cat = new Cat(); CatchMouseCommand catchMouseCommand = new CatchMouseCommand(cat); secretary.setCommand(catchMouseCommand); } public void createCrowCommand() { Cock cock = new Cock(); CrowCommand crowCommand = new CrowCommand(cock); secretary.setCommand(crowCommand); } }</span></span></span>

完毕了上述的基本类后。我们须要加入用户程序进行測试,例如以下

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public class UserApplication {/** 这是通常意义上的用户程序,也就是使用命令模式的上下文环境。* @param args*/public static void main(String[] args) {// TODO Auto-generated method stubSecretary secretary = new Secretary();//lion的作用相似Loader。为secretary(invoker)绑定命令对象。Lion lion = new Lion(secretary);lion.createCatchMouseOrder();secretary.publicCommand();lion.createCrowCommand();secretary.publicCommand();}
}</span></span></span>

从測试例中能够看到,lion为secretary动态绑定了不同的command,进而完毕请求參数化。在本例中当secretary接受到详细的command就会publiccommand(公布运行),实际在非常多场合,这个公布是由用户事件驱动的,比方某个MenuItem在初始化时绑定了相应功能。仅仅有在用户点击该MenuItem时,才会通过该菜单项的回调方法调用publiccommand,进而完毕实际的运行。
   该測试例运行结果例如以下:

Cat is catching mouse
    Cock is crowing

扩展场景

上述代码演示样例是命令模式的一个使用演示样例,只解答了官方定义中的前半部分。即“请求參数化”,软件实践中命令模式应用较为广泛的场景是文本编辑器或者是IDE用户界面的开发,以此为例解释下官方定义中所说的请求排队、记录日志以及可撤销操作。

“请求排队”

假设你在一个配置一般的机子上频繁操作eclipse,通常你会看到以下这种界面

 

后面一大推Waiting的任务,就是正在排队的请求,这个现象的原因是以下这样的图:

 

我们要求IDE运行的请求被封装成命令对象放置在工作队列中,每个空暇线程会得到并运行该命令对象,但资源有限。调度器须要限制可以使用的线程数量。当有新的线程空暇时。排队等待的命令对象才会被顺序运行,这就是命令模式在请求排队中的应用方式。

“记录请求日志”

这个主要是应用于大型数据库的管理操作中。对于本文所举的样例实际意义不大。在大型数据库的维护中,全部的操作修改都是以命令对象的方式进行的,有些修改必须是以事务的方式进行。由于这些修改彼此都是紧密联系的,对于经年累月的频繁修改,无法做到每次修改的数据库内容都做一次备份,那样须要太多资源,于是,把每次的修改命令对象以序列化方式保存(store)在磁盘上,每两个checkpoint点之间的修改,都保存起来。

这样在系统发生崩溃时,通过反序列化的方式从磁盘上装载(load)这些命令对象,进而完毕这些命令的undo操作。这样就完毕了数据库系统恢复。

完毕上述任务不仅须要命令对象支持序列化操作。并且对于Command接口也有了新的要求。例如以下

 

 

“可撤销undo”Ctrl+Z

还是举前文的样例。撤销操作要求对象回到命令运行前的状态,这就须要在Command接口中加入undo方法。

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">public interface Command {public abstract void execute();public abstract void undo(); //新增undo接口
}</span></span>

公鸡类Cock内部须要一个表示打鸣频率的实例。如果公鸡打鸣频率有高、中、低三种。通常情况下打鸣频率为低,可能每周打鸣2次。可是命令下达后,打鸣频率明显升高。达到每周7次,这就是新的Cock类

<span style="font-family:KaiTi_GB2312;font-size:18px;"><span style="font-family:KaiTi_GB2312;font-size:18px;">//公鸡类-receiver
public class Cock {public static final int LOW = 0;public static final int MEDIUM = 1;public static final int HIGH = 2;private int CrowFrequence; // 新添一个记录打鸣频率的状态实例,默觉得LOW//打鸣方法public void Crow() {System.out.println("Cock is crowing");setCrowFrequence(HIGH); // 调用打鸣方法后,频率为HIGH}//Getter和 Setter函数,获取和设置当前打鸣频率public int getCrowFrequence() {return CrowFrequence;}public void setCrowFrequence(int crowFrequence) {CrowFrequence = crowFrequence;}@Overridepublic String toString() {// TODO Auto-generated method stubreturn "CrowFrequence is " + CrowFrequence;}
}</span></span>

在详细命令中。须要加入一个状态来记录命令运行前的打鸣频率,在风头过后运行undo操作时,这个被记录的频率值就被恢复了。

<span style="font-family:KaiTi_GB2312;font-size:18px;">public class CrowCommand implements Command{Cock cock;int PrevStage;    //记录在命令运行前的打鸣频率状态public CrowCommand(Cock cock) {// TODO Auto-generated constructor stubthis.cock = cock;}@Overridepublic void execute() {// TODO Auto-generated method stubPrevStage = cock.getCrowFrequence(); //运行命令前记录。

System.out.println("before execute : " + cock); cock.Crow(); System.out.println("after execute : " + cock); } @Override public void undo() { // TODO Auto-generated method stub cock.setCrowFrequence(PrevStage); //运行undo操作,设置保持的prevStage System.out.println("after undo : " + cock); } }</span>

改写測试例加入运行undo操作,得到的结果例如以下

这样的undo情况比較简单,不过保存了一个实例域。并且只能够undo上一步,非常多时候须要建立操作的历史记录,这样就须要保存一个运行command的链表,每个链表中都含有能够撤销的命令及其详细实现,这个在类似word和PS之类的软件中应用的非常广泛,不做赘述。

“宏命令”

这是一种特殊的命令类。在该类中定义一连串的命令list,运行和撤销时。将该list中全部的命令都运行一遍。能够自己定义加入或者删除list中的详细命令,熟练使用word的同学都用过宏命令,比方毕业论文模板对于字体、段落、页眉页脚等全部格式的要求能够被简单定义成一个宏模板,仅仅要应用这个宏模板就能够高速自己主动的将当前文档设置成论文要求格式。

结构

该结构和上文uml图类似,可自行对照。

官方定义

效果及注意问题点

1)命令模式将请求调用者和实际运行这解耦,符合依赖倒置原则。

2)详细的command对象能够像其他对象一样进行扩展。

这是一个把函数抽象成类的特殊对象,较普通函数优势前文已述。

3)该结构符合开发封闭原则,加入新的类不须要修改原代码结构。

4)详细命令对象的智能程度怎样。这个命令对象是否一定依赖于receiver完毕操作,依赖程度是否会变化。

本文的详细对象是全然依赖于接受者的方法。

5)命令是否有必要进行undo操作。undo操作须要保持怎么的状态值。假设状态是较为复杂的对象,须要引入很多其他实例进行表示。在撤销过程中是否会引起接受者内部的其他变化。

与其它模式的关系

大型文本编辑软件的菜单树是分成复杂的,除了前文提到的宏命令以外,与组合模式相结合能够实现具有多层分支结构的菜单树命令。

当用来记录接受者的复杂状态时,能够使用备忘录模式。利用该模式能够完毕撤销操作后的状态恢复。

收尾

命令模式是在软件实践中应用较为广泛的一种模式,这个模式的应用场景较为特别,尤其是对于菜单树和数据库相关的功能模块中,这样的模式的长处明显,它分离了任务的请求者和运行者,把行为封装成对象,从而能够完毕类似请求參数化、状态保存/恢复、撤销、重做、宏命令等功能。该模式最典型的应用多在软件用户菜单树开发、事件驱动、数据库日志维护及恢复等将行为作为命令对象的场合。甚至当你为程序加入一个ActionBar.TabListener监听器对象时,框架层也用到了命令模式。

欢迎分享交流,共同进步~

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
注:欢迎分享,转载请声明~~
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

版权声明:本文博客原创文章,博客,未经同意,不得转载。

举例说,在命令模式(Command Pattern)相关推荐

  1. 设计模式:命令模式(Command Pattern)

    命令模式(Command Pattern): 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接受者是谁,也不知道请求的操作是哪个. 我们只需在程序运行时指定具体的请求接受者即可,此时 ...

  2. 设计模式 - 命令模式(command pattern) 撤销(undo) 具体解释

    命令模式(command pattern) 撤销(undo) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 參考命令模式: http://blog.cs ...

  3. 乐在其中设计模式(C#) - 命令模式(Command Pattern)

    原文:乐在其中设计模式(C#) - 命令模式(Command Pattern) [索引页] [源码下载] 乐在其中设计模式(C#) - 命令模式(Command Pattern) 作者:webabcd ...

  4. 设计模式系列3-----C++实现命令模式(Command Pattern)

    什么是命令模式? GoF的书的定义为:"Command pattern encapsulate request as an object, thereby letting you param ...

  5. 解读设计模式----命令模式(Command Pattern)

    ***本文与作者原文有一定的偏差,其中加入了一部分是个人看法,详细请查看作者原文.*** 原文连接http://www.dofactory.com/Patterns/PatternCommand.as ...

  6. 32命令模式(Command Pattern)

    耦合与变化:     耦合是软件不能抵御变化灾难的根本性原因.不仅实体对象与实体对象之间存在耦合关系,实体对象与行为操作之间也存在耦合关系.                               ...

  7. 命令模式(Command pattern)及代码实现

    模式定义: 将客户端的请求封装成一个对象(这个对象就是 命令对象),使请求的发送者 和请求的接收者 进行 责任分离解耦, 这样 两者只通过 命令进行交互;发送者 不关心 请求的具体实现细节, 接收者 ...

  8. 【白话设计模式八】命令模式(Command)

    为什么80%的码农都做不了架构师?>>>    #0 系列目录# 白话设计模式 工厂模式 单例模式 [白话设计模式一]简单工厂模式(Simple Factory) [白话设计模式二] ...

  9. c++命令模式command

    c++命令模式command 概念 角色和职责 案例 概念 Command模式也叫命令模式 ,是行为设计模式的一种.Command模式通过被称为 Command的类封装了对目标对象的调用行为以及调用参 ...

  10. C++设计模式--命令模式(Command)

    概述 命令模式的结构很简单,但是对于消除代码间的耦合却有着重要的影响.命令模式就是一个函数对象:一个作为对象的函数.通过将函数封装为对象,就能够以参数的形式将其传递给其他函数或者对象,告诉它们在旅行请 ...

最新文章

  1. ios UIImage 圆形图片剪切方案
  2. elasticsearch不能使用root启动问题解决
  3. HTML5学习笔记简明版(4):新元素之video,audio,meter,datalist,keygen,output
  4. php mysql 正则_MySQL 正则表达式
  5. 根据经纬度求最近点的三种解法java实现
  6. 聊聊2019年的web前端
  7. php windows 下载,PHPForWindows官方下载_PHPForWindows最新版_PHPForWindows7.2.1官方最新版-华军软件园...
  8. visualize python_安利一个Python大数据分析神器!
  9. react key值警告问题
  10. 实战制作U盘工具去除XP系统管理员密码
  11. 猿创征文|三维重建领域的开发者工具箱
  12. 素材之家,中国免费素材下载网站!下免费素材就到素材之家!
  13. 如何使用计算机做海报,用word做的海报步骤_word怎么设计海报
  14. 中软国际携手华为发力IT外包业务nbsp;…
  15. 计算机无法加入域请确保域名,计算机无法加入域的终级解决方法
  16. Myrio的各个接线口,有需要可以下载使用
  17. Windows下安装tdm-gcc(64位)
  18. sybase常用命令
  19. 论文写作英文翻译软件
  20. 华为小米联手开发鸿蒙,华为、小米、OPPO、OV联手打造应用商店,谷歌最担心的情况发生...

热门文章

  1. 鸿蒙osppt,Mate40 Pro鸿蒙OS快速上手体验+一点个人看法
  2. 电脑的基础知识_电脑入门基础知识
  3. 【赠书】新手速递!深度学习视频理解!
  4. 【知识星球】动态推理网络结构上新,不是所有的网络都是不变的
  5. 【完结】12篇文章告诉你深度学习理论应该学到什么水平
  6. 【AI白身境】Linux干活三板斧,shell、vim和git
  7. 全球及中国5-氯-2-羟基苯甲酸产业专项调研与投资潜力预测报告2022-2028年
  8. Windows Server 2008 R2 下配置证书服务器和HTTPS方式访问网站
  9. 2019 Multi-University Training Contest 1 - 1004 - Vacation - 二分 - 思维
  10. 渐变色 + 屏幕缩小自动产生滚动条