基于合同的软件架构模式

再次简化软件架构

营销中最重要的规则之一就是倾听其客户需求。 让我们考虑MVVM架构中View客户端的真实需求,注册模型变更通知,以便为他们提供最佳的编码体验,并定义一个架构合同,该合同将满足他们的需求,以严格遵守合同规则。

定义这组需求是为了通过减少此通知调用的外部可变性来减少软件的复杂性:为了获得流畅的体验,当上下文完全由以下内容确定时,只有一种可能的方法可以调用目标函数数据和线程的观点。

此外,该合同应完整并考虑与模型的所有交互:这样,合同就保护了目标功能背后的代码不受应用程序其余部分的影响。 与其编写复杂的代码来管理所有意外的可能情况以解决所有可能的执行情况,不如通过设计减少到唯一的情况并通过合同来强制执行,来减少可能的情况数量。 实施名义上的方案就足够了。

确定每个客户的需求

从市场细分的角度来看,有几种类型的客户:

1)查看内容客户端:

视图内容客户端是视图控制器的功能,负责基于某些模型属性渲染视图。

客户端需要通过其视图模型从模型中读取一组属性:因此,当这些读取属性发生更改时,必须通知客户端。

当所有读取属性都稳定时,客户端要求该通知仅在主线程事件循环中一次 同步发生 稳定意味着这些读取属性在事件循环结束之前不会更改。 这样可以保证所显示的内容与模型的当前状态完全匹配,而不是其先前状态的过时副本。

客户端可能还需要编写一些模型属性,其他View Content客户端将使用这些模型属性。 在属性写入后,这些订阅的View Content客户应收到其通知。 书面财产应作为合同的一部分。 不允许客户读取或写入不属于合同一部分的任何财产,因为这可能会破坏其他客户的“财产稳定”规则。

在渲染视图时,所有这些都发生在主线程中。

客户端还要求在事件循环结束之前不得删除其视图控制器,这意味着将在接收到此通知之前执行视图层次结构渲染。

该客户端还需要独立于模型对象的存在而注册模型属性,以便与模型对象创建/删除分离:每个模型属性均由源自根模型对象的唯一键路径标识。

这组需求是通知合同的基础:

“当这组现在稳定的READ属性中的至少一个发生更改时,我只希望同步地被通知一次,并且我可以读/写其中一些WRITE属性。”

因此,通知调度程序应基于这些联系来计算模型属性之间的依赖关系:如果合同读取A并写入B,则B依赖于A,这将创建模型属性顺序。 因此,通知调度程序应将合同级别计算为读取集的最高属性级别,并按此顺序进行调度。

2)查看层次结构客户端:

视图层次结构客户端是一种视图控制器功能,负责基于一组模型“视图层次结构”属性,通过创建/删除子视图层次结构来修改其子视图层次结构。

该客户端不应向其子视图控制器提供模型属性,而应仅向子视图控制器用于从模型中获取其模型属性的不可变配置数据。

由于视图层次结构更新将从根到叶发生,因此每个视图层次结构客户端均应提供与其层次结构级别相同的优先级编号:

  • 根视图控制器=优先级1
  • 其子级=优先级2
  • 它的子孙=优先级3
  • 等等…

通知调度程序将首先按优先级顺序发送基于优先级的通知,然后再发送与数据相关的通知(例如:查看内容)。

视图层次结构客户端可以写入任何不稳定的视图内容或视图层次结构属性,即不属于优先级相等或较低的优先级合同。

因此,合同如下:

“当该组现在稳定的READ(视图层次结构)属性中的至少一个发生更改时,我希望仅在此优先级级别上仅被同步通知一次,并且我可以读/写任何非稳定属性。”

3)标准化,协调,计算数据客户端:

规范化客户端负责解决跟随模型写事务的模型中的任何不一致问题。

协调客户端负责在高层确定应用程序/文档的视图层次结构和上下文,并设置视图层次结构属性。

计算数据客户端负责计算一组属性,这些属性可以由多个视图模型共享,并且应在呈现视图层次结构之前进行计算。

这些客户端具有基于优先级的合同,带有硬编码的负优先级,可以在View Hierarchy客户端之前安排它们。

  • 归一化=优先级-3
  • 协调=优先级-2
  • 计算数据客户端=优先级-1
  • 查看层次结构…优先级为正
  • 查看内容…无优先级/数据驱动

我们可以为客户提供优质服务吗?

当前有许多架构模式被大肆宣传,很明显,它们都试图满足其中一些需求。

据我所知,最接近上述需求的是单向Elm / React / Flux体系结构。

在这种健全的体系结构中,事件循环周期可分解为:

  • 诸如用户按下按钮之类的启动ACTION,它不会呈现视图。
  • 模型更新事务,其中更新了模型。
  • 一组通知会根据模型状态更改来更新视图,从而呈现视图层次结构。

我对该模型的经典实现的关注是:

  • 模型状态与视图之间的强耦合
  • 按正确的顺序安排通知
  • 模型状态之间交互的管理
  • 需要管理模型状态,而不是考虑应用程序/文档的状态是其所有属性的状态。

我在编写一个大型40KL macOS Swift应用程序时遇到了这个挑战,该应用程序具有与许多视图控制器交互的复杂模型。

因此,我编写了一个兼容iOS / macOS的Swift框架,该框架可以满足以下需求:Quantwm,并在Paris Cocoaheads:Cocoaheads演示文稿中进行了介绍。

Quantwm是一个通知调度框架,位于视图控制器和模型之间。

该模型负责公开静态属性路径树以进行注册,并且每个属性都应实现一个在每次更新时增加的更改计数器。 任何模型属性读/写都只能在主线程上进行。

在主线程ACTION上,执行Quantwm Write事务,在此期间,每个更新的模型属性的计数器都会递增,并且不会触发任何通知。

在Write事务结束时,Quantwm将查看所有已注册的客户端,检查相应模型属性的更新计数器,并根据注册合同以正确的顺序传递通知,首先是基于优先级的通知,然后根据数据依赖性。

在合同的保护下,将仅一次,确定性地同步,同步地通知客户,而魔术发生了:

  • 仅限同步线程和主线程:没有意外的后台线程或异步可变性。
  • 通知按顺序安排:标准化,协调,共享数据计算,视图层次结构,视图内容。
  • 自动考虑所有数据相关性。
  • 如果出现问题,则调用堆栈将同步公开直到该问题为止所执行的代码序列。
  • 所写的代码受合同保护:仅需执行合同定义的名义方案。 合同保证了通知的功能与应用程序其余部分之间的安全去耦。

如果您喜欢本文和这种基于合同的概念,欢迎您在Github上检查我的Quantwm框架并进行改进。