软件开发,经常需要考虑如何解除对象之间的耦合,当开发一个新的模块的时候,为了达到松耦合的目的,除了要依据五项原则(开闭原则、单一职责、依赖倒置、里氏替换、接口隔离)之外,同时也要考虑是否有合适的设计模式来套用。为了能够将设计模式应用于实际开发中,首先需要熟练掌握设计模式。
因此,本文将详细讲解一个多功能且优雅的设计模式,即命令模式,该模式虽然结构简单,但是应用场景多,模式结构优雅。首先介绍命令模式的基本结构,然后再结合具体的例子来了解命令模式的多种应用场景,包括烧烤、电机驱动、和主动对象模式。
基本命令模式
命令行模式的作用是解耦行为请求者和行为实现者。它将一个请求封装为一个命令对象,而命令对象则去调用行为实现者来完成实际的功能。
下图是基本命令模式的类图。invoker负责管理执行命令对象,Command为命令基类,每一个命令对象都需要继承命令基类,并且命令对象内部关联接受者Receiver, 调用Receiver来完成实际的功能。Invoker是行为请求者,Receiver则是行为实现者,两者通过命令对象实现了解耦。

具体代码的实现比较简单,这里不再再列出,只看下使用方法。先创建接受者,再将接受者作为参数来创建命令对象,然后创建调用者invoker, 通过调用者来设置命令对象,最后执行命令。

烧烤店
平时去比较正规的烧烤店吃烧烤的时候,一般会有服务员来负责订餐,而烤肉者则会在后厨根据服务员送过来的订单进行烤肉。

下来根据上面的类图结构来实现对应的功能,首先实现烤肉者类,BakeScallop表示烤扇贝,BackChickenWing表示烤鸡翅。

接着实现命令基类,构造函数接受烤肉者对象作为入参。

有了命令基类之后,再实现具体的命令类,分别为烤扇贝命令、烤鸡翅命令。


最后再定义服务员类,SetOrder设置命令对象,Notify通知烤肉者烤肉,CancelOrder取消订单,m_listCmd使用列表来存储所有订单。

接着实现服务员类,具体实现参见以下代码。

完成所有的功能代码之后,调用流程如下所示,可以看出其过程与上面实现的基本命令模式的过程是一致。JBBarbecuer烤肉者对应Receiver, JBChickenWingCmd和JBScallopCmd是具体的命令对象,JBWaiter服务员对应Invoker。

电机驱动
机械设备中,有的部件会由传感器来驱动电机运动,那么这里就可以使用到命令模式。通过命令模式来解耦传感器和电机。

首先定义电机命令基类,声明虚函数Do。

接着实现具体电机类,继承电机命令基类JSCommand。

最后实现传感器类,构造函数接受电机对象。Notify函数内部调用电机对象的Do方法。

最后来看下使用方法。通过命令模式,传感器不需要知道绑定的是什么电机,只需要执行命令方法即可。

主动对象
最后再来讲解一个命令对象的例子,该例子与ACE中讲解的Active Object模式不太一样,但是它对于单线程的环境下,对实现非阻塞运行,提供了一种很好的解决思路。
如下类图所示,JActiveBaseCommand是命令基类,所有具体命令对象都要继承该命令基类。JActiveEngine是一个命令引擎,负责增加管理命令对象,并且运行命令。

首先定义命令基类JActiveBaseCommand。

接着在实现命令引擎类,该类使用列表存储所有的命令对象。AddCmd增加命令对象到列表中。Run执行列表中的命令对象。

然后再分别实现具体命令对象。定义实现休眠类,该类的构造函数接受三个参数,第一个参数表示休眠的时长,第二个参数是命令引擎的指针,第三个参数是唤醒命令对象指针。而休眠类的一个关键点就是它的执行流程。命令引擎运行动休眠对象的时候,休眠对象判断是否首次运行,如果是的话,设置开始时间,并将自己重新加入命令引擎队列中,如果不是首次运行,并且没有到时,那么重新将自己加入命令引擎队列,否则,将唤醒命令类添加到命令引擎


定义实现上面使用到的唤醒命令类。

最后看下调用方式,新建命令引擎,新建唤醒命令对象,将它们作为休眠命令对象的构造参数来新建休眠命令对象,最后将休眠命令对象添加动命令引擎,运行命令引擎。

从上面的实现可以看出,休眠命令对象运行过程中,并没有阻塞主线程,而是判断是否到时,如果没有,那么将自己重新加入命令引擎。
基于上面的例子,再实现更多的命令类,实现随机打印字符信息,并且到时,自动停止打印信息。
首先实现定义停止命令类。

接着实现打印字符的命令类,该类的执行函数Execute中,首先打印字符,然后判断停止命令对象中的标记是否被置为true , 如果是,那么就退出。如果不是,将自己作为参数来新建休眠命令对象,并将该休眠命令对象加入命令引擎。


最后再看看实际实际调用方式,可以看出与上面的例子类似,只是添加更多的命令对象。但是这里没有考虑命令对象创建之后,如何释放的问题。如果程序整个生命周期,命令对象数目不多,那么可以常驻,否则,可以考虑使用智能指针来创建命令对象。

概括总结
本文首先讲解了基本的命令模式,说明命令模式能够解耦行为请求者和行为实现者,而关键就是命令对象,接着基于命令对象再讲解三个例子,分别为烧烤、电机驱动以及主动对象。三个例子中,比较特别的是主动对象,它体现了RTC(run-to-completion)的思想, 运行即完成,命令对象永远不会阻塞主线程。