美文网首页
FlowG框架的设计

FlowG框架的设计

作者: 科研者 | 来源:发表于2020-10-14 17:53 被阅读0次

FlowG框架 是面向流程编程范式的一个实现,本文主讲 FlowG 框架的设计,但由于时间关系 和 FlowG 暂时还未完全开发完成,所以本文并不是设计的全部,也不是终稿,还有很多很好的设计还没来得及记录,日后有空会继续完善;另外,本文也不涉及FlowG框架的使用教程和具体API。

目录

内容

FlowG框架的设计是依据 面向流程框架的设计 进行的,并有以下具体的设计;

1. FlowG的类图

为了清晰表示 FlowG 中角色之间的关系,我绘制了 FlowG 中部分角色之间的类图,如下所示:

FlowG类图

2. 流程

FlowG中的流程是指一系列元素和有向关系的集合;

流程应该具备以下特征:

  • 懶创建:用到再创建;
  • 数据始终沿着流程单向流动;若要将数据传递给上一个元素,则需要建立一个当前元素到一个元素的连接;
  • 可挂载用户自义对象,此对象在整个框架中可轻松访问,以便扩展 和 插件开发;
  • 支持两种模式:异步模式 和 同步模式;当传递一个 Promise 数据时,在异步模式下 会等待 Promise 决议之后再继续接下来的流程,在同步模式之下,会将 Promise 数据当作普通数据来对待,继续进行接下来的流程;这两种模式可以应用在整个流程上,也可以应用在单个元素上;当元素没有设置模式时,会采用流程中设置的模式;有了异步模式,可轻松且灵活地实现延时、定时、请求特特性;

2.1. 思考

  • 流程应该是由元素实例组成还是由元素的类组成?
    流程中同一类型的节点未必就是同一节点,就如流程图中的所有红色节点是同一类,但这些节点是不同的节点,所以在流程中呈现的是元素的实例而不是元素的类。所以流程应该是由元素的实例组成。

3. 元素

因为 节点 和 管道有许多共性,所以提供一个 节点 和 管道的抽象类别 叫 元素。即,节点 和 管道 都称为 元素

3.1. 特征

  • 具备 添加、移除下一个元素 的接口,以便与下一个元素进行连接 和 断开;
  • 具备输入、输出数据类型的描述,以便验证、代码检查等;由于元素可输入数据,也可输出数据,所以存在对数据类型的要求,若在连接时,前后元素的数据类型不一致,则将导致错误,所以,应该给元素加上输入、输出数据类型的描述,并在连接时验证数据类型是否吻合;
  • 可添加多个一个元素,即:可连接多个下一级元素,这可使 FlowG 更加灵活、强大;
  • 具备激活下一级元素的接口,调用该接口,将激活所有的下一级元素;
  • 具备激活接口,可激活当前元素;
  • 在首次激活时再进行初始化,以避免多余的计算;
  • 由于JavaScript类的构造机制是一段式构造(将来我会写专门的文章来讲JS类构造机制的问题以及解决方案),所以在父子类有相互依赖的初始化逻辑时,会有很多问题,所以,应该在基类提供机制解决,具体的解决方案会有专门的文章详解;
  • 元素应该具备唯一的ID,以便在任意地方都能引用到;
  • 元素ID的唯一性应该是全局的,不应该是模块范围内的,因为如果是模块范围内的,那么当把元素ID作为参数传递给其它模块时,则会导致查找不到 或 找到不一样的元素,这与 ID 的本意相违;

4. 节点

节点 是 程序的流程结构中每一步的逻辑单元,是使用者编写逻辑单元地方,它承载着程序的逻辑;

4.1. 特征

  • 继承自元素
  • 具备激活指定端口的接口,如:next(端口名字,数据)
  • 有生命周期:创建、销毁、聚焦、失焦;

5. 端口

端口是节点与其它元素的连接口;

5.1. 特征

  • 可设置多个输入端口、多个输出端口,每个端口具有名字,名字可以是字符串也可以是数字,即可以使用对象配置端口,也可以使用数组配置端口;
  • 输入端口支持提供一个转换函数用以当节点激活时自动转换数据;
  • 输出端口也支持提供一个转换函数,用以当激活输出端口时自动处理传递的数据,或者 将产生输出数据的逻辑放在输出端口的转换函数里,这样激活输出端口时便不需传递输出数据,这将使得端口更加解耦;
  • 当连接到节点时,可指定连接到节点的哪个端口;
  • 默认输出端口 就是 节点继承自元素的下一级元素的机制;
  • 既然 输入端口、输出端口 都需要转换函数,这跟管道的作用很类似了,那么,可直接将端口定义成管道类型;但为了更加灵活,决定将端口定义成元素类型,这意味着节点也可作端口;

5.2. 思考

  • 输入端口与其节点的连接与激活机制:当要激活节点的某个端口时,是由节点激活端口,还是先激活端口然后由端口来激活节点?
    • 方案1:如果用元素来表示端口的类型,则意味着端口可与节点用正常的连接方式来连接,这时候上一级元素更适合直接连接到端口,端口再连接到节点,激活时是先激活端口,再由端口激活节点,这样的改造成本较小,而且可以实现多个节点共享一个端口,这将带来更多的可能性;
    • 方案2:如果不用元素来表示端口的类型,则端口只能完全作为节点的一部分,只能由节点触发端口的逻辑,而且不能实现多节点共用一个端口;
      最终决定采用方案1。

6. 管道

当连接两个节点 或 两个节点的端口时,被连接的两个目标可能有不同的数据要求,仅有这一点,还不能实现节点间的完全解耦;可让目标通过通过 管道 来连接,管道可以负责数据的转换,甚至也可以主动传递数据,如:上一个节点没有输出数据,但下一个节点需要输入数据时,这样可以让连接请求数据后,再传给目标;管道里也可以做延迟操作等等;

总之,管道 就是用来在节点之间起连接作用并可传递、转换数据的。

6.1. 特征

  • 继承自元素
  • 转换数据
  • 能生成数据:比如,请求数据;
  • 能延迟激活下一级元素;
  • 通过提供转换函数来实现转换数据,这样,生成数据的功能也可能转换函数来实现,再配合异步模式,让转换函数返回 延迟的 Promise ,也可方便地实现延迟激活下一级元素;

7. 包装元素

包装元素是一种可以将一个流程F 作为一个元素 E 来对待,当激活元素 E 时,就激活了这个流程F,当流程F 结束时(即:流程F没有下一个元素可激活时)会自动触发E的下一个元素以使流程继续。

7.1. 特征

  • 因为元素可任意连接,所以多个流程可以有共同的元素;由于任何元素都可单独激活,并且多个流程可以共享流程分支,所以,如果手动激活包装流程的某个元素 并且 没有指定包装元素,则接下来的流程激活过程应该是在和在没有包装元素的情况下包装流程单独运行的一样,即包装流程执行结束后不再继续执行包装元素后的元素;

    因为如果非要指定一个默认的 flow ,容易导致意外的流程触发,比如:
    假如 流程 A->B->C 被多个包装元素共用,当我们拿到独立的流程 A->B->C 并激活后,流程走到C后,会触发 C 中设置的默认 包装元素 ,这就导致 默认包装元素的下一级元素被激活,这不是我们想要的;因为我们只看到了 A->B->C

  • 因为包装元素表现的像一个元素,所以包装元素也有输入数据类型 和 输出数据类型;而包装流程的终点元素的输出数据类型未必和包装元素的输出数据类型一致,所以包装元素自身需要一个转换函数来将包装流程的终点元素的输出数据类型转成包装元素的输出数据类型;
  • 因为包装元素也需要转换函数,这就使得包装元素和管道的结构很像,所以包装元素可以作这管道的一个子类;
  • 包装元素和管道的转换函数的执行时机有些不同,管道是在一激活时就执行转换函数,而包装元素是在激活时执行流程,当流程结束后再执行转换函数,这点区别可以通过重载来解决;

7.2. 包装流程结束后继续包装元素的方案

若要实现包装流程结束后,继续继续包装元素的下一级元素,则意味着包装流程的终点元素需要知道包装元素 或者 包装元素的下一级元素;为了解耦,建议让包装流程的终点元素知道其包装元素。

实现这种机制,有以下几种方案:

  1. 让包装流程的终点元素引用包装元素;但获取流程的终点元素有点麻烦,且可能要引入新的机制;
  2. 让包装流程的每一个元素引用包装元素;
    这种方案有以下几个问题:
    • 在配置包装元素时就要遍历包装流程的每一个元素,但这个包装流程可能根本不会执行,造成无效运算;
    • 在包装元素移除包装流程时还需要删除包装流程的每个元素上对包装元素的引用;
    • 如果包装流程有更新,比如:增加新的节点,那这些更新的元素也需要设置对包装流程的引用,总之,维护成本较高;
    • 无法解决多个包装元素共享包装流程的情况;
    • 对于手动激活包装流程的元素的情况,当包装流程执行完毕后,还会继续其引用的包装元素的下一级元素,如果要禁止这种情况,则需要引用新的标识 或 机制;
  3. 只有包装流程运行时再将包装元素传递给包装流程,并且会在包装流程的每一个元素上传递;这个方案能解决以上方案的所有问题;并且实现起来简单;

因为第3种方案能解决其它方案的所有问题;并且实现起来简单;所以最终采用第3种方案;

8. 模块

  • 模块负责元素的类型注册 和 实例缓存;修正:实例缓存 应该是全局共享的,不应该在模块中缓存
  • 父模块的元素类型集应该与子模块共享,即:如果在模块上获取元素类型,则应该能获取到 当前模块 和 父模块中注册的元素类型
  • 子模块的元素实例 应该与 父模块共享,即:如果在模块上获取元素实例,则应该获取到该模块 及 所以子模块的实例;修正:实例缓存 应该是全局共享的,不应该在模块中缓存。
  • 关于流程的配置应该放在模块上;
  • 对于子模块没有的配置会继承父模块的,对于子模块已有的配置会覆盖父模块的;

9. 描述者

为了让流程支持懒创建机制,则需要一种数据来描述流程的元素实例及元素与元素之间的有向关系,我称这个这种描述性数据结构为 描述者。用来描述元素实例的描述者 称为 元素描述者;用来描述流程的描述者 称为 流程描述者

9.1. 元素描述者

  • 可通过元素的类型、ID来描述实例;元素的类型可以是类型的名字、或者构造函数等;
  • 因为元素的类型是注册在模块中的,所以描述者需要引用模块,以便知道在哪个模块中查找元素类型;
  • 应有获取元素实例的接口,以便通过描述者获取元素实例;

9.2. 流程描述者

  • 描述的是流程中元素与元素的有向关系,比如:当前元素的下一级元素是谁;
  • 因为要描述流程的元素与元素之间的有向关系,所以既要描述出元素,又要描述出有向关系;既然这样,可直接在元素描述者的数据结构上加个有向关系的描述,这样就形成了流程描述者;

10. Mixin

为了让 FlowG 更具灵活性、扩展性、复用性,则需要提供 Mixin 机制;

  • Mixin 混入的目标是元素;
  • 模块要具备 元素的 Mixin 混入 接口;
  • 元素的各级父子类都应该支持 Mixin 接口;

因为模块 和 元素类都需要 Mixin 机制,所以可将 Mixin 机制抽离出来单独实现;有以下抽离方案:

  • 抽离成类,需要具备 Mixin 机制的类(如模块、元素)都继承该类;
  • 抽离成装饰器;需要具备 Mixin 机制的类(如模块、元素)需要被该装饰器装饰;

11. 元素选项

为了增强元素的可配置性、扩展性、复用性,创建元素实例所需的配置选项有以下来源:

  • 创建元素实例时传入构建函数的选项;
  • 元素类及父类、超类上的混入的选项;
  • 元素的所属模块及其父模块上注入的选项;

这些分散在多种、多级、每级又有多个的选项在需要被用时需要进行合并;

为了提高性能,每种、每级都应该适当缓存合并结果,并在必须时才进行合并运算;

12. 单元

因为 FlowG 框架中好多角色都树形的,如:元素:引用下一级元素,描述者:引用下一级元素的描述者;所以还需要抽离出一个像这样具备下一级结构的基本类,我称这个类为 单元

12.1. 特征

  • 能引用多个下一级数据;
  • 具备添加、删除下一级数据的接口;
  • 其它的公共逻辑;

13. 构建流程的方式

  • 手动连接实例:元素实例支持添加下一级元素
  • 配置对象:可通过流程描述者来实现
  • 形象化语言:通过 FlowG语言 来构建流程

相关文章

网友评论

      本文标题:FlowG框架的设计

      本文链接:https://www.haomeiwen.com/subject/zxbwpktx.html