设计模式 ——— 命令模式

意图

简单的说,命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化(即,可以用不同的命令对象,去参数化配置客户的请求);对请求排队或记录请求日志,以及支持可撤销的操作。

这一模式的关键是一个抽象的Command类,它定义了一个执行操作的接口。其最简单的形式是一个抽象的Execute操作。具体的Command子类将接收者作为其一个实例变量,并实现Execute操作,指定接收者采取的动作。而接收者有执行该请求所需的具体信息。 接收者:真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。

结构

  • Command: 定义命令的接口,声明执行的方法。
  • ConcreteCommand: 命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • Receiver: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • Invoker: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
  • Client: 创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。

流程

a) Client创建一个ConcreteCommand对象并指定它的Receiver对象。 b) 某Invoker对象存储该ConcreteCommand对象。 c) 该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,CnocreteCommand就在执行Excute操作之前存储当前状态以用于取消该命令。 d) ConcreteCommand对象会调用它的Receiver的一些操作以执行该请求。

可撤销的操作

可撤销操作的意思就是:放弃该操作,回到未执行该操作前的状态。 有两种基本的思路来实现可撤销的操作: ① 一种是补偿式,又称反操作式 比如被撤销的操作是加的功能, 那撤消的实现就变成减的功能;同理被撤销的操作是打开的功能,那么撤销的实现就变成关闭的功能。 ② 另外一种方式是存储恢复式 意思就是把操作前的状态记录下来,然后要撤销操作的时候就直接恢复回去就可以了。

认识命令模式

(1)命令模式的关键 命令模式的关键之处就是把请求封装成为对象,也就是命令对象,并定义了统一的执行操作的接口,这个命令对象可以被存储、转发、记录、处理、撤销等,整个命令模式都是围绕这个对象在进行。 (2)命令模式的组装和调用 在命令模式中经常会有一个命令的组装者,用它来维护命令的“虚”实现和真实实现之间的关系。如果是超级智能的命令,也就是说命令对象自己完全实现好了,不需要接收者,那就是命令模式的退化,不需要接收者,自然也不需要组装者了。 而真正的用户就是具体化请求的内容,然后提交请求进行触发就好了。真正的用户会通过invoker来触发命令。 在实际开发过程中,Client和Invoker可以融合在一起,由客户在使用命令模式的时候,先进行命令对象和接收者的组装,组装完成后,就可以调用命令执行请求。 (3)命令模式的接收者 接收者可以是任意的类,对它没有什么特殊要求,这个对象知道如何真正执行命令的操作,执行时是从command的实现类里面转调过来。 一个接收者对象可以处理多个命令,接收者和命令之间没有约定的对应关系。接收者提供的方法个数、名称、功能和命令中的可以不一样,只要能够通过调用接收者的方法来实现命令对应的功能就可以了。 (4)智能命令 在标准的命令模式里面,命令的实现类是没有真正实现命令要求的功能的,真正执行命令的功能的是接收者。 如果命令的实现对象比较智能,它自己就能真实地实现命令要求的功能,而不再需要调用接收者,那么这种情况就称为智能命令。 也可以有半智能的命令,命令对象知道部分实现,其它的还是需要调用接收者来完成,也就是说命令的功 能由命令对象和接收者共同来完成。 (5)发起请求的对象和真正实现的对象是解耦的 请求究竟由谁处理,如何处理,发起请求的对象是不知道的,也就是发起请求的对象和真正实现的对象是解耦的。发起请求的对象只管发出命令,其它的就不管了。

命令模式的更多用途

命令模式的关键之处就是把请求封装成为对象,也就是命令对象(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。我们可以利用这样的特性衍生一些应用,例如:线程池、工作队列、日志请求等。

  1. 队列请求 想象有一个工作队列:你在某一端添加命令,然后另一端则是线程。线程进行下面的动作:从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令...... 请注意,工作队列和命令对象之间是完全解耦的。此刻线程可能在进行财务运算,下一刻却在读取网络数据。工作队列对象不在乎到底做些什么,它们只知道取出命令对象,然后调用其execute()方法。类似地,它们只要实现命令模式的对象,就可以放入队列里,当线程可用时,就调用此对象的execute()方法。
  2. 日志请求 某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。
参考

《Head First 设计模式》 《设计模式:可复用面向对象软件的基础》 《研磨设计模式》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java 成神之路

使用 NIO 实现 echo 服务器

4617
来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

2968
来自专栏大内老A

The .NET of Tomorrow

Ed Charbeneau(http://developer.telerik.com/featured/the-net-of-tomorrow/) Exciti...

31710
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

2707
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3135
来自专栏魂祭心

原 canvas绘制clock

4064
来自专栏闻道于事

js登录滑动验证,不滑动无法登陆

js的判断这里是根据滑块的位置进行判断,应该是用一个flag判断 <%@ page language="java" contentType="text/html...

6798
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

2645
来自专栏跟着阿笨一起玩NET

c#实现打印功能

2762
来自专栏C#

DotNet加密方式解析--非对称加密

    新年新气象,也希望新年可以挣大钱。不管今年年底会不会跟去年一样,满怀抱负却又壮志未酬。(不过没事,我已为各位卜上一卦,卦象显示各位都能挣钱...)...

4878

扫码关注云+社区