C/C++思考:命令模式在控制中的应用

意义

先说结论:命令模式能有效对代码模块进行解耦,增强模块的扩展性以及可移植性。

问题

为了方便说明问题,假设现在有一个电机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)对象中,实现了请求调用者和具体实现者之间的解耦。

可将枚举类型的命令看作一个对象,实现请求调用者与具体实现者的解耦。因为,决策者希望电机旋转,但又不必了解电机如何旋转;实现者不关心各种输入信号,只关心执行何种命令,如何控制电机端细节。
可见,基本思想是相同的。

posted @ 2023-05-12 18:02  明明1109  阅读(108)  评论(0编辑  收藏  举报