Clean 架构下的现代 Android 架构指南
Clean 架构是 Uncle Bob 提出的一种软件架构,Bob 大叔同时也是 SOLID 原则的命名者。
Clean 架构图如下:
这张图描述的是整个软件系统的架构,而不是单体软件,其中至少包括服务端以及客户端。
对于 Android 单体应用开发来说应该还需要一个更贴切更精确的 Clean 架构图。
我大概总结了一下过往的开发经验,找出了应用架构中的重要部分,然后绘制了下面这张 Clean 架构指导下的 Android 应用架构图:
以及一般数据流向图:
依赖关系
Clean 架构基本准则是源码级别的内层不依赖外层,依赖关系永远是单向的,外层向内层依赖。
如上,Model 层是没有任何依赖的,UseCase 可以依赖 Model 和 Repo 等,但绝不能依赖 ViewModel,UI 层依赖 ViewModel,但 ViewModel 绝不能依赖 UI 层。
为了达到这种源码级别的依赖关系,我们必须借助一些工具来实现依赖注入,一般可以使用 Hilt 或者 Koin 这样的框架来实现。
另外,依赖注入不应该被滥用,不是所有的对象都适合用依赖注入,只有那些有明确层次关系的模块,互相有着明确的依赖关系的才需要。对于一些工具类,显然是没必要注入的。
Model(领域模型)
业务模型,或者叫领域模型,是根据软件业务设计出来的具体模型,一般来说会是个 data class
,其中不包含任何业务逻辑,只是个单纯的模型对象。
由于是在整个架构的最内层,所以不依赖任何其他模块,并且相对稳定,设计的时候需要考虑这点。如果模型发生变化,那意味着整个上层的依赖方都可能发生变化,需要重新测试。
在命名和包结构上,领域模型不需要带 Entity 之类的后缀,直接命名为像 User 一样即可,但考虑到这是在软件的最内层,可能会被所有模块依赖到,所以要尽可能贴近其设计目标,并且不能太过宽泛。在包结构上,需要被存放在 model 包下面。
Adapter(数据适配器)
数据适配器层主要用来做数据转换,主要有两个职责:
- 转换网络接口实体数据类和领域模型。
- 领域模型之间的互相转换。
Adapter 层也比较纯粹,只负责简单的数据转换,而且对外暴漏的函数都是幂等函数。
如果数据转换过程中涉及到复杂的业务逻辑,可以考虑先用 UseCase 处理完成后再交给 Adapter。但因为 Adapter 层比 UseCase 层更靠内,所以 Adapter 不能依赖 UseCase。
习惯上,我们会以待转换类为开头,Adapter 结尾命名,例如我们要把 UserEntity
转换为 User
, 那么应该这么写:
class UserEntityAdapter{
fun toUser(entity: UserEntity): User {
//...
}
}
Repo
对于我们 Android 开发来说,Repo 层应该是对网络接口或本地磁盘的数据读写的封装,对于 Repo 的使用者来说,不需要关注具体的实现,且 Repo 中一般不具备复杂的业务逻辑,只能包含简单的数据处理逻辑。
Repo 应当隐藏具体的实现细节,不仅包括获取方式是网络还是本地数据,也应该隐藏对应的实体数据类,这意味着 **Repo 层对外暴漏的函数的入参和出参不能包含接口返回的实体类,也不应该包含数据库表实体类,只能包含领域模型或者基本类型。**我们给 Room 设计的数据库表的 data class
应该限制在 Repo 内部,我们给 Retrofit 设计的接口返回数据 data class
也同样应该限制在 Repo 内部。
data class UserEntity