从任务中心看状态机功能组件设计

本文经过设计一个简单的任务中心来展现一个使用有限状态机思想设计该如何作。前端

本文使用 JSDOC 添加注释。git

关于有限状态机

有限状态机 (FSM) 维基 百度百科 是用来表示有限个状态及在这些状态之间转移和动做的数学模型。早在07年就有文章讲解 js 与状态机的结合。github

在程序中,咱们用这种数学模型抽象业务的复杂行为。做为状态机,有如下特征特色:bash

  1. 事物拥有的状态是有限的。
  2. 事物在任一时刻,只处在一种状态。
  3. 能够经过触发事物的某些行为,能够致使事物从一种状态过渡到另一种状态。
  4. 一种行为通常只能将其余状态变为一种特定状态,但不能将其变为多种状态。

设计实现组件

任务中心具备如下功能:架构

  • 任务中心能够在任什么时候刻加入新任务
  • 任务中心在启动时才会运行任务
  • 任务在任务中心运行时会被即时执行
  • 任务中心能够被终止,暂停,恢复

若是针对以上需求,整理整个流程, 能够认为任务中心的流程以下:测试

由于需求中存在“能够在任什么时候间塞入任务”,因此对于不一样的流程设定任务的不一样执行状态。ui

  1. 任务中心建立后便可容许塞入任务
  2. 任务中心运行状态中,任何任务塞入会被即时执行
  3. 任务中心暂停或中止时,任何任务塞入也不会被执行
  4. 任务中心恢复运行时,会执行全部以前塞入并无被执行的任务

分离了任务中心和任务,这两种并行的状态,咱们就能够分开设计,分开编码了。编码

那么先作任务中心组件的实际设计,关于这个组件可能有的状态以下:spa

  1. 初始状态设计

  2. 运行状态

  3. 暂停状态

  4. 恢复状态

  5. 恢复后的运行状态

  6. 中止状态

  7. 重启状态

........

但因为咱们组件有的功能,咱们仅为组件设计了四个状态就能够描述其整个生命流程:

  1. IDLE 初始化状态
  2. RUNNING 运行状态
  3. PAUSED 暂停状态
  4. STOPPED 中止状态

由于咱们的这个任务中心是给任务调度用的,因此仅跟任务有关的状态才是有用的状态,这也是状态机设计思惟的核心之一,仅设计使用相关联的状态, 并保证剩余的状态可成为完整的流程, 任务中心的四个状态组成流程以下图所示:

实现组件

描述状态

为了更好的展现事务状态,咱们在组件状态切换中加入明确的状态标识。并在类的状态列表定义它们:

/** * 枚举 InstantTask 组件状态 * @enum {Number} * @readonly */
InstantTask.State = Object.freeze({
  /** * @memberof InstantTask.State * @type {Number} */
  IDLE: 0,
  /** * @memberof InstantTask.State * @type {Number} */
  RUNNING: 1,
  /** * @memberof InstantTask.State * @type {Number} */
  PAUSED: 2,
  /** * @memberof InstantTask.State * @type {Number} */
  STOPPED: 3,
});
复制代码

描述行为

组件一共存在五个行为, 这些行为在数学中能够被认为是变换器,它们就是上述状态机运转图的具体行为体现。

  1. start()

    用于启动组件,设置组件为 RUNNING 状态。

    上一个状态能够是任何状态。

    用于启动任务中心,并会执行全部以前塞入并无被执行的任务

  2. pause()

    用于暂停组件,设置组件为 PAUSED 状态。

    上一个状态只能是 RUNNING 状态。

    用于暂停任务中心,在暂停后任何任务塞入也不会被执行

  3. resume()

    用于恢复组件的暂停状态,设置组件为 RUNNING 状态

    上一个状态只能是 PAUSED 状态

    用于启动任务中心,并会执行全部以前塞入并无被执行的任务,和start()之间功能差距不大,可是不会像start()同样重置全部设定,并且主要用途是和pause方法作一一对应。一个方法或者状态的多用途对于状态机设计来讲会增长架构复杂度,尽可能避免吧。

  4. stop()

    用于中止任务中心,设置组件为 STOPPED 状态

    上一个状态只能是 RUNNING 状态

    用于中止任务中心,和pause的区别是它会记录中止状态,而且不能被resume,和pause内部逻辑差距不大。主用途是和start()作对立方法。

  5. reset()

    用于重置任务中心,设置组件为 IDLE 状态

    上一个状态能够是任何状态, 但主要是 STOPPED 状态

    用于重置任务中心,此方法等于生成一个新的任务中心。

实现组件

当咱们肯定组件的抽象运行状态和运行行为后,就能够结合实际业务逻辑来实现具体的组件了。 本文中使用 EventEmitter 来做为事件的广播和处理,前端也能够借用 eventemitter3 这样的近似的类实现一样的功能。

同时接下来本文使用代码中注释的方式继续说明每一个方法的详细用途:

具体代码已经放在: https://gist.github.com/Suixinlei/4b2da4ee1ef84e89b5cfccc1b88b3e4f

测试代码放在: https://gist.github.com/Suixinlei/d8441babe5174b7b1d4326f39b0fcff2

测试结果以下所示,咱们能够看到实际运行结果和咱们的抽象设计彻底一致,充分证实抽象设计对最终逻辑的影响:

添加任务 instant1
添加任务 instant2
任务中心开始运行
instant1 run
instant2 run
添加任务 instant3
instant3 run
任务中心已暂停
添加任务 instant4
恢复运行
instant4 run
复制代码

拓展延伸

目前来讲,全部任务都是即时任务,因此对于任务中心的状态仅有四种便可知足需求。若是这些任务有一些是定时任务,甚至能够循环执行并规定执行次数呢?读完这篇文章我想你心中应该已经有些想法, 那么看看下面的最终实现和你想的是否有些出入呢?

这里是最终实现: https://gist.github.com/Suixinlei/e245812799fa160cdf0bbcdf279e68bc

总结

使用 FSM 设计组件能够

  1. 让组件变得逻辑清晰
  2. 组件逻辑和业务逻辑分离,从概念上有良好的分层结构
  3. 组件易测试,组件实现初期仅需测试组件逻辑部分而非复杂的业务逻辑
  4. 梳理组件逻辑闭环,能够较容易的发现组件生命周期中缺乏的部分,反推业务进行改进

关注查看更多原创内容

关注公众号投递简历 (招聘视觉、交互、前端)

相关文章
相关标签/搜索