设计模式 ——— 命令模式

意图

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

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

这一模式的关键是一个抽象的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 条评论
登录 后参与评论

相关文章

来自专栏情情说

RabbitMQ实战:理解消息通信

前段时间总结完了「深入浅出MyBatis」系列,对MyBatis有了更全面和深入的了解,在掘金社区也收到了一些博友的喜欢,很高兴。另外,短暂的陪产假就要结束了,...

41312
来自专栏趣谈编程

用户空间和内核空间是什么?

学习 Linux 时,经常可以看到两个词:User space(用户空间)和 Kernel space(内核空间)。

1543
来自专栏程序员互动联盟

【高级编程】linux进程间通信总结

1. 概览 本文记录经典的IPC:pipes, FIFOs, message queues, semaphores, and shared memory。 2....

3357
来自专栏智能算法

Python学习(九)---- python中的线程

原文地址: https://blog.csdn.net/fgf00/article/details/52773459 编辑:智能算法,欢迎关注! 上期我们一起学...

1062
来自专栏IMWeb前端团队

Nodejs进阶:核心模块net入门与实例讲解

模块概览 net模块是同样是nodejs的核心模块。在http模块概览里提到,http.Server继承了net.Server,此外,http客户端与http服...

2266
来自专栏肖洒的博客

TCP/IP(一)

IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因...

622
来自专栏你不就像风一样

Java性能调优工具(Linux、Windows篇)

top命令的输出可以分为两部分:前半部分是系统统计信息,后半部分是进程信息。在统计信息中,

802
来自专栏IT笔记

Nginx + Shiro + Redis 实现负载均衡集群(成绩报告查询系统升级篇)

写在开始 上一篇讲到使用Ehcache实现分布式缓存,尽管其直接操作JVM内存,速度快,效率高,但是缓存同步麻烦,分布式集群配置不方便,如果应用服务器重启会丢失...

2777
来自专栏匠心独运的博客

消息中间件—RocketMQ消息消费(一)

文章摘要:在发送消息给RocketMQ后,消费者需要消费。消息的消费比发送要复杂一些,那么RocketMQ是如何来做的呢? 在RocketMQ系列文章的前面几...

1343
来自专栏决胜机器学习

优化页面访问速度(三) ——服务端优化

服务端的优化,主要可以通过消息队列、减少数据库请求(缓存)、并发处理、页面静态化等方式处理。

782

扫码关注云+社区