[译] Android 架构:Part 3 —— 应用 Clean Architecture

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

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

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

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

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 模块。 你可能会发现另一种更合适的分法。

我们将实体、用例、仓库和设备接口分到领域模块。如果你想要一个额外的挑战(奖励是永恒的荣耀和完全整洁的设计),你可以使该模块成为一个纯 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 架构系列 第四部分

原文

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容