命令模式思想与 EvSim 事件调度

命令模式把一次操作请求封装成对象,使请求能够被保存、传递、排队,并在合适的时候执行。本文结合 EvSim 的事件机制,说明事件如何表达动作,以及事件队列和世界对象如何共同完成调度。

在程序中,执行一个动作最直接的方式,就是调用一个函数。例如,让车辆移动,就调用移动函数;让雷达探测,就调用探测函数。这种方式简单清楚,适合立即执行的操作。

随着系统变复杂,我们往往需要进一步管理这些动作:有的动作要等到指定时间执行,有的动作需要按优先级排序,有的动作要周期性重复,还有的动作在执行前可能被取消。此时,程序需要同时处理动作的实现和动作的组织方式。

例如,让某个组件在仿真时间 10 秒时更新一次,可以被封装成一个事件对象。这个对象记录执行时间、优先级、操作目标,并提供统一的执行入口。系统先保存这个对象,等时间到达,再调用它的执行方法。这样,提出请求和执行请求就可以发生在不同的位置、不同的时间。

在 EvSim 项目中,EvEvent 承担了命令对象的角色。它的核心接口可以简化为下面的形式。

复制代码
class EvEvent
{
public:
    enum class EvDisposition
    {
        Delete,
        Reschedule
    };
    virtual ~EvEvent() = default;
    virtual EvDisposition execute() = 0;
};

这里最关键的是 execute()。不同的事件可以执行完全不同的操作,但都通过这个统一入口被调用。调度代码只需要持有 EvEvent 指针,就能管理多种具体事件。

EvEvent 还包含执行时间 time、优先级 priority、执行开关 shouldExecute 等信息。这些字段让一次操作请求具备了可调度、可控制的形式。

命令模式中的几个角色,在项目中可以这样对应。

|----------|-----------------------------------------|--------------------|
| 模式角色 | 项目中的对应对象 | 职责 |
| 命令接口 | EvEvent | 规定统一的执行入口 |
| 具体命令 | EvComponentUpdateEvent EvOneShotEvent 等 | 实现具体操作 |
| 调用者 | EvWorld | 在合适的时机调用 execute() |
| 接收者 | EvComponent 或回调操作的业务对象 | 完成实际业务工作 |
| 请求创建方 | 组件调度代码或其他业务代码 | 创建并提交事件 |

这里还需要一个保存待执行命令的地方,项目使用的是 EvEventQueue。EvEventQueue 内部使用 unique_ptr 持有多个事件对象,并按照执行时间、优先级、入队顺序排序。时间越早,越先出队;时间相同,优先级数值越小,越先出队;前两项都相同,则按照入队先后排序。EvEventQueue 是命令的组织容器,EvWorld 承担执行调度的职责。仿真推进时,EvWorld 从队列中取出到期事件,检查其是否允许执行,再调用 execute()。单线程调度路径逐个执行事件;并行调度路径还会根据优先级和 affinityKey 进行分批与分组。

以组件周期更新为例,组件通过调度代码创建 EvComponentUpdateEvent,把更新请求加入队列。到达计划时间后,世界对象调用事件的 execute(),事件再调用组件的更新逻辑。下面是其中的关键流程,省略了生命周期和状态检查。

复制代码
// 执行本次组件更新

if (!com->executeScheduledUpdate(time(), mEpoch))

{

    return EvDisposition::Delete;

}



// 安排下一次执行时间

setTime(time() + com->updateInterval());

return EvDisposition::Reschedule;

如果组件的更新间隔是 0.1 秒,那么一次执行结束后,事件会把自己的执行时间增加 0.1 秒,并返回 Reschedule。EvWorld 收到这个结果,再把事件放回队列,于是形成连续的周期更新。当组件不再满足执行条件时,事件返回 Delete,该事件对象随之释放。

这个过程体现了命令模式的价值:业务对象专注于具体动作,调度系统统一管理执行时机,以及执行后的处理方式。

组件负责更新自身状态,事件负责包装这次更新请求,队列负责组织待执行请求,世界对象负责推动执行。各部分通过明确的接口协作,减少了调度代码对具体业务的依赖。

以后增加一种新的操作,例如某个时刻触发一次探测,只要提供相应的具体事件,并提交给现有调度机制,就可以复用事件队列和执行流程。调度系统仍然通过统一的 execute() 接口调用它。

命令也可以包装函数,而不必为每一个小动作都编写新的事件类。项目中的 EvOneShotEvent 接收一个回调,执行后结束;EvRecurringEvent 则允许回调决定是否再次调度。这使系统既能使用专门的事件类表达复杂行为,也能用回调表达简单操作。

排队、延迟执行、周期调度,是这个项目在命令对象基础上实现的能力。其他系统还可以为命令增加执行记录、重试或撤销功能,但这些都需要额外设计。例如,要撤销一次操作,就需要保存操作前的状态,或提供能够恢复状态的逆向操作。当前 EvEvent 的公共接口主要围绕执行与重新调度展开。

命令模式也有使用成本。一次简单调用被包装成对象后,会增加事件创建、对象生命周期和调用路径的管理工作。适合采用它的场景,通常是操作请求确实需要统一排队、延后执行或集中调度。

在 EvSim 中,仿真时间推进、事件优先级和组件周期更新都需要这种统一管理。因此,把动作封装为 EvEvent,再交给 EvEventQueue 和 EvWorld 处理,让不同的仿真行为能够接入同一套调度机制。后续扩展新的行为时,主要增加具体事件及其业务逻辑,已有调度流程就能继续发挥作用。

相关推荐
加载力科技17 天前
Mac 打开 ISO 文件完整教程:3 种方法挂载与解压,挂载失败也能提取文件
macos·命令模式·磁盘工具·mac打开iso·hdiutil·iso解压·mac技巧
lingchen190623 天前
Windows CMD 的 `cd`(change directory,切换目录)命令
windows·命令模式
Lost of 程序猿1 个月前
命令模式实战:把“下单后的动作“打包成可执行的任务
后端·设计模式·c#·asp.net·命令模式
加载力科技1 个月前
硬盘咔咔响是坏盘信号!赶在数据丢失前克隆换盘:不重装系统、不丢文件
windows·嵌入式硬件·系统安全·策略模式·命令模式·硬盘
czhc11400756632 个月前
2026-09-10 综合博客:异步异常 + 排查核实思维
命令模式
geovindu2 个月前
CSharp: Command Pattern
开发语言·后端·c#·.net·.netcore·命令模式·行为模式
Java后端的Ai之路2 个月前
21、Python - 命令模式
开发语言·人工智能·python·命令模式·外观模式
Flutter OH2 个月前
Flutter OH 卡死冻屏问题定位指南
flutter·命令模式
AI人工智能+电脑小能手2 个月前
大白话说Java设计模式-34-命令模式(业务实战篇)
java·spring·设计模式·命令模式·异步任务·撤销重做·事务封装