目录
意义
先说结论:命令模式能有效对代码模块进行解耦,增强模块的扩展性以及可移植性。
问题
为了方便说明问题,假设现在有一个电机Motor,在识别到外界信号后,通过软件控制其旋转。需要支持的旋转方式为3种:正转、反转、振动。
最直接,也是最传统方法,是通过软件设置一个电机控制器MotorController模块(或类),周期性读取从外界信号,当信号满足一定条件时,就驱动电机按所想方式旋转。如下图所示:
注:上面框图省略了控制电机的驱动电路
这种模式优点是简单、直接,但有个缺点是MotorController不仅要控制电机,还绑定了具体的输入信号signal1、signal2、signalN。这就导致了,一旦信号种类、个数,甚至决策方式发生改变,就需要修改MotorController代码。
而电机的旋转方式,实际上跟信号本身并无直接关系,信号只是触发条件。如果把电机控制这部分功能由MotorController实现,而信号决策专门抽象出来形成新的模块ModuleA~ModuleM(根据具体项目需要来决定),决策模块通过向电机控制模块发生命令,以驱动电机。从而实现信号决策与电机控制解耦。
用命令模式解决电机控制与输入信号耦合问题
根据上面的分析,我们可以设置2个模块:
1)决策模块,专门用来处理输入信号,决定向电机控制模块发送何种(请求)命令。因为输入信号可能由多个模块产生,又有不同组合,因此,可能存在多个决策模块实例。
2)电机控制模块,专门用来接收控制命令,然后根据当前电机状态来决定如何执行命令,也可以不执行。因为电机(控制电路)通常只有一个,所以电机控制模块通常只有一个实例。
于是,可以写出如下伪代码:
- 电机控制模块
// 电机控制命令
typedef enum : int{
Command_None = 0, // 空命令
Command_CW, // 正转
Command_CCW, // 反转
Command_SHAKE, // 振动
} Command;
/// 电机控制模块
class MotorController {
public:
// 定时轮询接口, 提供电机控制任务main函数入口
static void mainTask();
void sendCommand(Command command);
private:
void handleCommand();
Command currentCommand_;
Command newCommand_;
};
- 决策模块
这里只写2个作为示意:
class ModuleA {
public:
void receiveSignal(int signalNo, int signalVal);
static void mainTask();
};
class ModuleB {
public:
void receiveSignal(int signalNo, int signalVal);
static void mainTask();
};
实践中, 通常具体的决策模块只会接收固定的信号。如果对应信号产生源,则便于程序员理解。例如,车载ESP产生车速信号,BCM产生带扣信号,那么可以在程序中设置虚拟的ESP、BCM模块,作为控制模块,专门分别用来接收车速信号、带扣信号,决策是否向电机控制模块发送控制命令。
与标准命令模式的区别异同
当然,这并非标准的命令模式。标准命令模式通常包含了执行命令的代码,而我们上面用来控制电机的命令,仅仅是一个枚举类型,并不包含任何绑定的函数。
然而,我们仔细分析命令模式,其意图是这样的:
命令模式通过将请求封装到一个命令(Command)对象中,实现了请求调用者和具体实现者之间的解耦。
可以将枚举类型的命令看做一个对象,实现请求调用者与具体实现者的解耦。因为,决策者希望电机旋转,但又不必了解电机如何旋转;实现者不关心各种输入信号,只关心执行何种命令,如何控制电机端细节。
可见,其基本思想是相同点。