到目前为止, 在这个系列中, 我们已经介绍了一些初学者的错误, 并通过了 Clean 架构. 在最后一部分中, 我们将介绍拼图的最后一部分: 标签, 或者更确切地说: 组件.
首先, 我将删除我们在 Android 项目中不使用的东西, 然后添加一些我们使用的东西, 但在原始的 Bob 叔叔图中找不到. 它看起来像这样:
我会从最抽象的中心走到边缘.
Entities
Entities, 即 Domain 对象或业务对象, 它们是 App 的核心. 它们代表了 APP 的主要功能, 您应该能够通过查看它们来了解 APP 的功能. 它们包含业务逻辑, 但仅限于它们 - 验证和类似的东西. 他们不会与外在世界的细节进行互动, 也不会处理持久性. 例如, 如果您有新闻 APP, 则这些实体将是类别, 文章和商业广告.
Use cases
Use case, 又名 interactors, 又名 business services, 是 Entities 的扩展, 是业务逻辑的延伸, 也就是说. 它们包含的逻辑不仅限于一个实体, 而是处理更多的实体. 一个好 Use case 的一个指标是, 你可以用一种通用的语言描述它在一个简单的句子中的作用 - 例如,"从一个账户转移到另一个账户". 你甚至可以用这样的命名来命名该类, 例如, TransferMoneyUseCase.
Repositories
Repositories 用于坚持 Entities. 就这么简单. 它们被定义为接口并用作想要对 Entities 执行 CRUD 操作的用例的输出端口. 另外, 它们可以公开一些与持久性相关的更复杂的操作, 例如过滤, 聚合等. 具体的持久性策略, 例如数据库或因特网, 是在外层实现的. 例如, 您可以命名接口 AccountRepository.
Presenters
如果你熟悉 MVP 模式, Presenters 就会做你期望他们做的事情. 他们处理用户交互, 调用适当的业务逻辑, 并将数据发送到 UI 进行渲染. 这里通常有不同类型的模型之间的映射. 有些人会在这里使用 Controller, 这很好. 我们使用的 Presenter 正式被称为监督 Controller. 我们通常在每个屏幕上定义一个或两个 Presenters, 具体取决于屏幕, Presenter 的生命周期与视图的生命周期相关联. 一条建议: 尝试在 Presenter 上将您的方法命名为技术不可知论者(technology agnostic). 假设您不知道视图实现的技术. 因此, 如果您在视图中具有名为 onSubmitOrderButtonClicked 和 onUserListItemSelected 的方法, 则处理这些事件的相应演示者方法可以命名为 submitOrder 和 selectUser.
Device
这个组件在通知 (再次) 例子中已经被取笑了. 它包含了诸如传感器, 警报, 通知, 播放器, 各种 Managers 等 Android 提供东西的实现. 它是一个由两部分组成的部分. 第一部分是业务逻辑用作与外部世界进行通信的输出端口的内部圈定义的接口. 第二部分, 这是在图中绘制的, 是这些接口的实现. 因此, 您可以定义例如名为 Gyroscope,Alarm,Notifications 和 Player 的接口. 注意名称是抽象的和技术不可知 (technology agnostic) 的. 业务逻辑不关心如何显示通知, 玩家如何播放声音, 或陀螺仪数据来自何处. 您可以制作将通知写入终端, 将声音数据写入日志或从预定义文件收集陀螺仪数据的实现. 这样的实现对于调试或创建确定性环境是非常有用的. 但是, 您当然必须制作诸如 AndroidAlarm,NativePlayer 等实现. 在大多数情况下, 这些实现只是围绕 Android 的 Manager 类的包装.
DB & API
将存储库的实现放在这个组件中. 所有这些底层持久化的东西应该在这里: DAO,ORM 的东西, Retrofit(或别的东西)的东西, JSON 解析等. 你也可以在这里实现缓存策略, 或者只是使用内存中的持久性, 直到完成与应用程序的其余部分. 我们最近在团队中进行了一次有趣的讨论. 问题是这样的: 资源库是否应公开诸如 fetchUsersOffline(fetchUsersFromCache)和 fetchUsersOnline(fetchUsersFromInternet)之类的方法? 换句话说, 业务逻辑应该知道数据来自哪里? 看过这篇文章的所有内容后, 答案很简单: 不. 但是有一个问题! 如果关于数据源的决定是业务逻辑的一部分 - 例如, 如果用户可以选择它, 或者如果您有明确的离线模式的应用程序, 那么您可以添加这样的区别. 但是我不会为每个抓取定义两种方法. 我可能会在资源库中公开方法, 如 enterOfflineMode 和 exitOfflineMode. 或者, 如果它适用于所有存储库, 则可以使用输入和退出方法定义 OfflineMode 接口, 并在业务逻辑端使用它, 让存储库查询该模式并在内部进行确定.
UI
将与 Android 用户界面相关的所有内容放在这里 Activities,fragments,views,adapters 等完成.
Modules
下图显示了我们如何将所有这些组件分成 Android Studio 模块. 你可能会发现另一个更合适的部门.
我们将 entities,use cases,repositories 和 device 接口分组到 domain 模块中. 如果您想获得额外的挑战, 并获得永恒的荣耀和完全干净的设计, 您可以使该模块成为纯 Java 模块. 它会阻止你在这里使用快捷方式并将相关的东西放在 Android 上.
device 模块应该包含与 Android 相关的所有内容, 而不是数据持久性和 UI. 正如我们已经说过的, data 模块应该包含与数据持久性相关的所有内容. 你不能让这两个人进入 Java 模块, 因为他们需要访问各种 Android 的东西. 你可以将它们变成 Android 库.
最后, 我们将与 UI 相关的所有内容 (包括 presenters) 分组到 UI 模块中. 您可以明确地命名为 UI, 但由于 Android 中的所有东西, 我们将其命名为 "app", 就像 Android Studio 在创建项目时命名的那样.
这样更好些了吗?
为了回答这个问题, 我会抛弃 Bob 叔叔的图表, 并将之前描述的组件扩展到像我们用来评估以前类型的体系结构的图表. 这样做后, 我们得到这个:
现在让我们应用我们在以前的体系结构中使用的相同条件.
它完全由模块级别, 封装级别和类级别分开. 所以 SRP 应该得到满足.
我们尽可能将 Android 和现实世界推到了最外面. 业务逻辑不再直接接触 Android.
我们有很好的分离类, 很容易测试. 接触世界的类可以使用 Android 测试用例进行测试; 可以使用 JUnit 测试它. 有人恶意可能会称这类爆炸. 我称之为可测试的.:)
Clean 架构: 它可能很复杂 - 但它是值得的
我希望我精心挑选的标准并不是为了支持 Clean 架构而编造的, 它会让你尝试一下. 这看起来很复杂, 而且这里有很多细节, 但它是值得的. 一旦你将所有东西连接起来, 测试就容易得多, 错误更容易隔离, 新特性易于添加, 代码更具可读性和可维护性, 一切正常, 世界满意.
所以, 就是这样. 如果您还没有这样做, 请查看以前的系列文章: Mistakes http://five.agency/android-architecture-part-1-every-new-beginning-is-hard/ 和 Clean Architecture http://five.agency/android-architecture-part-2-clean-architecture/ .. 如果您有任何意见或问题, 请在下面留言. 我们总是有兴趣听到你的想法.
阅读我们的 Android Architecture 系列的第四部分.
您还可以查看其他部分:
- Part 5: How to Test Clean Architecture http://five.agency/android-architecture-part-5-test-clean-architecture/
- Part 2: The Clean Architecture http://five.agency/android-architecture-part-2-clean-architecture/
- Part 1: every new beginning is hard http://five.agency/android-architecture-part-1-every-new-beginning-is-hard/
来源: https://juejin.im/entry/5af80707f265da0b967250d4