到目前为止,在这个系列中,咱们已经讲解了一些 初学者易犯的错误,以介绍了整洁架构。在这最后一部分,咱们会介绍最后一个难题:标签,或者准确地说,组件。java
译者注:看完了这一部分,还有第四部分。在第四部分将会提供一个很酷的示范项目。android
首先,我会移除在 Android 项目中不使用的东西,添加一些在 Uncle Bob 的图中找不到的但咱们须要使用的东西,看起来像这样:数据库
我会从中心(抽象)讲到边缘(具体)。缓存
实体(又称领域对象或业务对象)是 app 的核心。它们表明 app 的主要功能,你应该可以仅经过查看实体来讲出 app 是作什么的。它们包含业务逻辑 —— 仅限于验证和相似的东西。它们不和真实的外部世界交互,也不会处理持久化。若是你有一个新闻 app,那么实体就是类别、文章和商业。网络
用例,也称为 interactor(交互器),也能够叫作 business service (业务服务对象),是实体的扩展,是 business logic(业务逻辑)的扩展。也就是说,它们包含的业务逻辑不限于一个实体,而是能够处理不少实体。一个好用例的标准是,你可使用一句简单的经常使用语言来描述它是作什么的,例如,“把钱从一个帐户转移到另外一个帐户”。你甚至可使用这样的命名系统来命名该类,例如 TransferMoneyUseCase。架构
仓库用于持久化实体。就这么简单。它们定义为接口,而且用在要对实体执行增删查改操做的用例的输出端口。此外,它们能够公开一些与持久化相关的复杂操做,譬如过滤、聚合等。具体持久化策略,譬如数据库或网络,在外层中实现。例如,你能够把接口命名为 AccountRepository。app
若是你熟悉 MVP 模式,Presenter 作你想让它作的事情。它们处理用户交互,调用恰当的业务逻辑,并将数据发送给 UI 渲染。这里一般有各类类型的模型之间的映射转换。有些人会在这里使用控制器,这是能够的。咱们使用的 Presenter 被正式称为监督控制器,咱们一般根据屏幕方向为每一个界面定义一个或两个 Presenter,而且 Presenter 的生命周期和相关的 View 的生命周期绑定。一个建议:尝试以技术无关的方式命名 Presenter 中的方法,伪装你不知道 View 是用什么技术实现的。因此,若是在 View 中有方法名为 onSubmitOrderButtonClicked 和 onUserListItemSelected,那么处理这些事件的相应的 Presenter 中的方法能够被命名为 submitOrder 和 selectUser。post
这个组件在先前通知那个例子中已经被玩坏了。它包含了诸如传感器、闹钟、通知、播放器、各类 *Manager 等等真实 Android 功能的实现。它包含两部分组件。第一部分是定义在内层的接口,业务逻辑用它来做为和外部世界通讯的输出端口。第二部分,也画在图中,是那些接口的实现。所以,好比,你能够定义名为 Gyroscope, Alarm, Notifications, 和 Player 的接口。请注意,这些名称是抽象的技术无关的。业务逻辑不关心通知如何显示,播放器如何播放声音,或螺旋仪的数据来自哪里。你能够建立一个将通知写入终端,将声音数据写到日志,或者从预先定义好的文件中收集螺旋仪数据的实现。这样的实现对于调试或建立一个用于你编码的肯定性的环境是颇有用的。固然,你必须建立诸如 AndroidAlarm,NativePlayer 等等的实现。在大多数状况下,这些实现仅仅是 Android Manager 类的包装。测试
这里没有哲学。将仓库的实现放在此组件中。全部的底层持久化的东西应该放在这里:DAO,ORM,Retrofit(或别的),JSON 解析等等。你还能够在这里实现缓存策略或者简单地在内存中(in-memory)持久化,直到你完成了 app 的其他部分。咱们团队最近进行了一个有趣的讨论。问题是这样:仓库是否应该公开诸如 fetchUsersOffline(fetchUsersFromCache)和 fetchUsersOnline(fetchUsersFromInternet)之类的方法?换句话说,业务逻辑是否应该知道数据来自哪里。读完这篇文章的全部内容后,答案很简单:不。但这里有个陷阱。若是关于数据源的决策是业务逻辑的一部分 —— 譬如,用户能够选择或者 app 有一个明确的离线模式 —— 而后你能够添加这样的区分。但我不会为每一个请求定义两种方法。我可能会在仓库中公开 enterOfflineMode 和 exitOfflineMode 这样的方法。或者若是它适用于全部仓库,咱们可使用 enter 和 exit 方法定义一个 OfflineMode 接口,并在业务端使用它,让仓库去查询它的模式而且在内部决策。fetch
这里的哲学更少。将和 Android UI 相关的东西放在这里。Activity、Fragment、View、Adapter 等等。完了。
下图显示了咱们如何将全部这些组件分解成 Android Studio 模块。 你可能会发现另外一种更合适的分法。
咱们将实体、用例、仓库和设备接口分到领域模块。若是你想要一个额外的挑战(奖励是永恒的荣耀和彻底整洁的设计),你可使该模块成为一个纯 java 模块。这将阻止你走捷径将一些 Android 相关的东西放在这里。
设备模块包含全部和 Android 相关的东西(除了数据持久化和 UI)。数据模块应该持有和数据持久化相关的东西,正如咱们说过的那样。你不能把这二者弄成 java 模块,由于它们须要访问各类 Andriod 相关的东西。你能够把它们弄成 Android library。
最后,咱们将和 UI (包括 Presenter)相关的全部东西分到 UI 模块。你能够明确地将其命名为 UI,可是因为全部 Android 的东西都在这里,咱们保留它 “app” 的名字,正如 Android Studio 在建立项目时所命名的那样。
为了回答这个问题,我丢开 Uncle Bob 的图,将先前描述的组件摊开到图中,就像以前那些咱们曾经评估过的架构类型那样。这样作以后,咱们获得:
如今让咱们来使用与以前的架构相同的评价标准。
它彻底分离到模块级别、包级别、类级别,因此应该知足单一职责原则。
咱们已经将 Android 和真实世界的东西尽量地推到边缘,业务逻辑再也没有直接接触 Android。
咱们很好地分离类以方便测试。接触 Android 世界的类可使用 Android 测试例进行测试,没有接触的类可使用 JUnit 进行测试。可能有人恶意称它为类爆炸,我称之为可测试。:)
我但愿我精心挑选的标准,不只能迎合 Clean Architecture,也能说服你一试。看起来很复杂,并且有不少细节,但这是值得的。一旦你把全部都串起来,测试就会变的更容易,BUG 更容易定位,新功能更容易添加,代码更易读和维护,一切均可以完美运行,宇宙都被知足了。
因此,就是这样。若是你尚未这样作,请看本系列先前的文章:初学者易犯的错误 和 介绍 Clean Architecture。若是你有任何意见或问题,请留言。咱们老是有兴趣听到你的想法。
阅读 Andriod 架构系列 第四部分