最新消息:欢迎访问Android开发中文站!商务联系微信:loading_in

[译] Android 架构:Part 3 —— 应用整洁架构

热点资讯 loading 295浏览 0评论

到目前为止,在这个系列中,我们已经讲解了一些 初学者易犯的错误,以介绍了整洁架构。在这最后一部分,我们会介绍最后一个难题:标签,或者准确地说,组件。

译者注:看完了这一部分,还有第四部分。在第四部分将会提供一个很酷的示范项目。

首先,我会移除在 Android 项目中不使用的东西,添加一些在 Uncle
Bob 的图中找不到的但我们需要使用的东西,看起来像这样:

1.jpg

我会从中心(抽象)讲到边缘(具体)。

Entities

实体(又称领域对象或业务对象)是 app 的核心。它们代表 app 的主要功能,你应该能够仅通过查看实体来说出 app 是做什么的。它们包含业务逻辑 —— 仅限于验证和类似的东西。它们不和真实的外部世界交互,也不会处理持久化。如果你有一个新闻 app,那么实体就是类别、文章和商业。

Use Cases

用例,也称为 interactor(交互器),也可以叫做 business service (业务服务对象),是实体的扩展,是 business logic(业务逻辑)的扩展。也就是说,它们包含的业务逻辑不限于一个实体,而是可以处理很多实体。一个好用例的标准是,你可以使用一句简单的常用语言来描述它是做什么的,例如,“把钱从一个账户转移到另一个账户”。你甚至可以使用这样的命名系统来命名该类,例如 TransferMoneyUseCase。

Repositories

仓库用于持久化实体。就这么简单。它们定义为接口,并且用在要对实体执行增删查改操作的用例的输出端口。此外,它们可以公开一些与持久化相关的复杂操作,譬如过滤、聚合等。具体持久化策略,譬如数据库或网络,在外层中实现。例如,你可以把接口命名为 AccountRepository。

Presenters

如果你熟悉 MVP 模式,Presenter 做你想让它做的事情。它们处理用户交互,调用恰当的业务逻辑,并将数据发送给 UI 渲染。这里通常有各种类型的模型之间的映射转换。有些人会在这里使用控制器,这是可以的。我们使用的 Presenter 被正式称为监督控制器,我们通常根据屏幕方向为每个界面定义一个或两个 Presenter,并且 Presenter 的生命周期和相关的 View 的生命周期绑定。一个建议:尝试以技术无关的方式命名 Presenter 中的方法,假装你不知道 View 是用什么技术实现的。所以,如果在 View 中有方法名为 onSubmitOrderButtonClicked 和 onUserListItemSelected,那么处理这些事件的相应的 Presenter 中的方法可以被命名为 submitOrder 和 selectUser。

Device

这个组件在先前通知那个例子中已经被玩坏了。它包含了诸如传感器、闹钟、通知、播放器、各种 *Manager 等等真实 Android 功能的实现。它包含两部分组件。第一部分是定义在内层的接口,业务逻辑用它来作为和外部世界通信的输出端口。第二部分,也画在图中,是那些接口的实现。因此,比如,你可以定义名为 Gyroscope, Alarm, Notifications, 和 Player 的接口。请注意,这些名称是抽象的技术无关的。业务逻辑不关心通知如何显示,播放器如何播放声音,或螺旋仪的数据来自哪里。你可以创建一个将通知写入终端,将声音数据写到日志,或者从预先定义好的文件中收集螺旋仪数据的实现。这样的实现对于调试或创建一个用于你编码的确定性的环境是很有用的。当然,你必须创建诸如 AndroidAlarm,NativePlayer 等等的实现。在大多数情况下,这些实现仅仅是 Android Manager 类的包装。

DB & API

这里没有哲学。将仓库的实现放在此组件中。所有的底层持久化的东西应该放在这里:DAO,ORM,Retrofit(或别的),JSON 解析等等。你还可以在这里实现缓存策略或者简单地在内存中(in-memory)持久化,直到你完成了 app 的其余部分。我们团队最近进行了一个有趣的讨论。问题是这样:仓库是否应该公开诸如 fetchUsersOffline(fetchUsersFromCache)和 fetchUsersOnline(fetchUsersFromInternet)之类的方法?换句话说,业务逻辑是否应该知道数据来自哪里。读完这篇文章的所有内容后,答案很简单:不。但这里有个陷阱。如果关于数据源的决策是业务逻辑的一部分 —— 譬如,用户可以选择或者 app 有一个明确的离线模式 —— 然后你可以添加这样的区分。但我不会为每个请求定义两种方法。我可能会在仓库中公开 enterOfflineMode 和 exitOfflineMode 这样的方法。或者如果它适用于所有仓库,我们可以使用 enter 和 exit 方法定义一个 OfflineMode 接口,并在业务端使用它,让仓库去查询它的模式并且在内部决策。

UI

这里的哲学更少。将和 Android UI 相关的东西放在这里。Activity、Fragment、View、Adapter 等等。完了。

模块(Modules)

下图显示了我们如何将所有这些组件分解成 Android Studio 模块。 你可能会发现另一种更合适的分法。

2.jpg

我们将实体、用例、仓库和设备接口分到领域模块。如果你想要一个额外的挑战(奖励是永恒的荣耀和完全整洁的设计),你可以使该模块成为一个纯 java 模块。这将阻止你走捷径将一些 Android 相关的东西放在这里。

设备模块包含所有和 Android 相关的东西(除了数据持久化和 UI)。数据模块应该持有和数据持久化相关的东西,正如我们说过的那样。你不能把这两者弄成 java 模块,因为它们需要访问各种 Andriod 相关的东西。你可以把它们弄成 Android library。

最后,我们将和 UI (包括 Presenter)相关的所有东西分到 UI 模块。你可以明确地将其命名为 UI,但是由于所有 Android 的东西都在这里,我们保留它 “app” 的名字,正如 Android Studio 在创建项目时所命名的那样。

好点了吗?

为了回答这个问题,我丢开 Uncle Bob 的图,将先前描述的组件摊开到图中,就像之前那些我们曾经评估过的架构类型那样。这样做之后,我们得到:

3.jpg

现在让我们来使用与之前的架构相同的评价标准。

4.jpg

它完全分离到模块级别、包级别、类级别,所以应该满足单一职责原则。

5.jpg

我们已经将 Android 和真实世界的东西尽可能地推到边缘,业务逻辑再也没有直接接触 Android。

6.jpg

我们很好地分离类以方便测试。接触 Android 世界的类可以使用 Android 测试例进行测试,没有接触的类可以使用 JUnit 进行测试。可能有人恶意称它为类爆炸,我称之为可测试。:)

7.jpg

这可能很复杂——但值得

我希望我精心挑选的标准,不仅能迎合 Clean Architecture,也能说服你一试。看起来很复杂,而且有很多细节,但这是值得的。一旦你把所有都串起来,测试就会变的更容易,BUG 更容易定位,新功能更容易添加,代码更易读和维护,一切都可以完美运行,宇宙都被满足了。

所以,就是这样。如果你还没有这样做,请看本系列先前的文章:初学者易犯的错误 和 介绍 Clean Architecture。如果你有任何意见或问题,请留言。我们总是有兴趣听到你的想法。

转载请注明:Android开发中文站 » [译] Android 架构:Part 3 —— 应用整洁架构

您必须 登录 才能发表评论!