本系列文章如下:
Flutter App架构
Flutter 架构设计通用准则
Flutter 架构设计最佳实践
Flutter App架构案例
本章中将会app开发架构设计的通用准则和这些准则在Flutter中的应用。
职责分离
职责分离是应用程序开发中的一个核心原则,它通过将应用程序的功能划分为不同的、自成一体的单元来促进模块化和可维护性。从另一个维度来看,就是将UI逻辑和业务逻辑分离。这就是常说的 分层 架构。在每一层中,进一步按照功能和特性划分。例如,将登录认证和搜索业务逻辑分开。
在 Flutter 中,这也适用于 UI 层中的部件。您应该编写可重用、精简的部件,尽可能减少逻辑。
分层架构
Flutter应用也应该是 分层 的。分层架构是一种将应用按照功能和职责划分为不同的层级的软件设计模式。典型的应用会按照复杂程度划分为2~3个层级。
- UI层 - 将业务Logic层的数据显示给用户并且处理用户交互。也被称为表现层(presentation layer)。
- Logic层 - 实现核心业务逻辑,并且处理UI层和Data层交互。也被称为域层(domain layer)。这一层是可选的,这取决于应用是否需要实现复杂的业务逻辑。如果只是展示信息通常不需要这层
- Data层 - 负责处理数据,例如数据库或者Flutter platform plugin数据。将数据暴露给上一层。
这里的每一层数据只能和自己的上一层或者下一层交互。UI层不应该感知Data层的存在,反之亦然。
单一真相来源
App中的每种数据应该只有一个来源(single source of truth SSOT)。SSOT用于管理本地和远程的数据。如果数据还会在本地修改,那么SSOT就应该是唯一负责提供数据的类。
这会显著的降低应用中bug的数量,并且也不会导致同一份数据的多个copy。
通常通过 Repository 类来完成相应数据的SSOT功能,这个类属于数据层。每种类型的数据都有对应的repository。
单向数据流动
单向数据流动(Unidirectional data flow, UDF)用于将状态和UI解耦。简单来说就是状态从数据层通过逻辑层最终流向UI层。用户交互事件则相反。
在UDF模式下,用户交互引起的UI刷新交互循环如下:
- 【UI层】根据用户交互产生的事件,例如button点击。在Widget点击的callback中调用逻辑层暴露的方法。
- 【Logic层】逻辑层中的类通过repository暴露的方法处理这个事件相关的数据。
- 【Data层】repository根据需要更新相应数据并向Logic层提供新的数据。
- 【Logic层】logic层保存即将发送到UI层的状态。
- 【UI层】UI层更新状态。
新的数据也可以从data层产生。例如,repository可以向HTTP服务端请求新的数据。这种情况下,只需要完成上述流程的后半段即可。需要关注的是所有的数据改变都发生在SSOT中,即data层中repository。这有助于编写出更容易理解,更少出错并且防止创建不必要或异常的数据。
UI是一个状态不可变的函数
Flutter是声明式的,即根据当前app的状态构建UI。当状态改变的时候,app应该根据新的状态触发UI重建。这就是在Flutter中经常听到的“UI是状态的函数”
这里面的重点是数据驱动UI,而不是其他因素。数据应当是不可变的和持久化的,view中应当包含尽可能少的逻辑。数据持久化的意思是app关闭后丢失的数据尽可能少,这有助于app的更加易于测试和抵御bug。
可扩展性
架构设计的每一层都应当定义合理的输入和输出。例如,logic层中的view model只能接受data层的repository作为输入,同时向UI层暴露事件指令和格式化后的数据表示的状态。
使用这样的逻辑清晰地接口会在后续更换具体的其他层的实现的时候变得非常容易。
可测试性
良好的可扩展性也会带来良好的可测试性。例如,可以mock一个repository来测试view model的逻辑是否正常。view model的测试不需要mock应用的其他部分,并且可以将UI逻辑和Flutter widget测试分开来。
这样的应用会在添加新的UI和逻辑的时候更加灵活和降低风险。例如增加一个新的view model不会影响data层或其他逻辑层。