[译] Android 架构:Part 2 —— 介绍 Clean Architecture

在本系列的第一部分,咱们介绍了咱们在寻找可行架构的道路上所犯过的错误。在这部分,咱们将介绍传说中的 Clean Architecture。android

当你在谷歌搜索 "clean architecture" 时,你看到的第一张图片是:数据库

它也被称为洋葱架构,由于图看起来象个洋葱(你会意识到你须要写样板代码写到哭);或者是端口和适配器,由于你能够看到右图的一些端口。六角架构是另外一个类似的架构。网络

Clean Architecture 是前面提到的 Uncle Bob 的心血结晶,他是 《代码整洁之道》的做者。这种方法的要点是,业务逻辑(也称为 domain),是宇宙的核心。架构

掌控你的领域(domain)

当你打开项目时,你应该已经知道这个 app 是作什么的,与技术无关。其它一切都是实现细节。譬如,持久化就是一个细节。定义接口,建立一个快速的粗糙的内存内(in-memory)实现,不要想太多,直到完成业务。而后你能够决定怎样真正地持久化数据。数据库,网络,二者结合,文件系统 —— 或者仍然保留在内存中,或者结果你根本不须要持久化。总之一句话:内层包含业务逻辑,外层包含实现细节。app

话说回来, Clean Architectue 有一些特性使这成为可能:dom

  1. 依赖规则
  2. 抽象
  3. 层与层之间的通讯

I.依赖规则

依赖规则能够用下图解释:布局

外层应该依赖内层。那三个在红色框框内的箭头表示依赖。与其使用“依赖”,也许使用“看见”、“知道”、“了解”这类术语更好。在这些术语中,外层看见,知道,了解内层,但内层看不见,也不知道,更不了解外层。正如咱们先前所说,内存包含业务逻辑,外层包含实现细节。遵循依赖规则,业务逻辑既看不到,也不知道,更不了解实现细节。这正是咱们努力想要作到的。post

如何实现依赖规则取决于你。你能够把它们放到不一样的包,但当心“内层的”包不要使用“外层的”包。然而,若是有人不知道依赖规则,没有什么能够阻止他破坏规则。一个更好的方法是把层分离到不一样的 Android 模块(modules,即子项目),并在构建文件(build.grale)中调整依赖,这样内层就没法依赖外层。ui

还有值得一提的是,虽然没人能够阻止你跨层依赖,譬如蓝色的层的组件使用红色的层的组件,但我强烈建议你只访问相邻的层的组件。3d

II.抽象

抽象原则以前已有所暗示。也就是说,当你朝图中间移动时,东西变得更抽象。 这是有道理的:正如咱们所说内层包含业务逻辑,而外层包含实现细节。

甚至能够在多个层之间划分相同的逻辑组件,如图所示。 内层定义更抽象的部分,外层定义更具体的部分。

举个例子说清楚些。咱们能够定义一个 “Notifications” 的抽象接口,并将其放到内层,这样你的业务逻辑须要时可使用它来向用户显示通知。另外一方面,咱们能够这样来实现该接口,即便用 Android NotificationManager 显示通知来实现,并把该实现放到外层。

以这种方式,业务逻辑可使用这样的功能 —— 通知(在咱们的例子中)—— 但它不了解实现细节:实际的通知是如何实现的。此外,业务逻辑甚至不知道实现细节的存在。来看下面这张图片:

当将抽象规则和依赖规则组合在一块儿时,结果是使用通知的抽象业务逻辑既不会看到,也不会知道,更不会了解使用 Android NotificationManager 的具体实现。这很好,由于咱们能够在业务逻辑绝不知情的状况下切换具体实现。

让咱们把这种规则组合和标准的三层架构简单对比下,看看它们各自的抽象和依赖是怎样的以及如何工做的。

在图中,你能够看到,标准三层架构的全部依赖最终都传到数据库。也就是说,抽象和依赖(方向)并不一致。在逻辑上,业务层应该是 app 的中心,但它却不是,由于依赖朝向数据库。

业务层不该该知道数据库,应该反过来。在 Clean Architecture 中,依赖朝向业务层(内层),而且抽象也上升到业务层,所以它们很好地匹配。

这是重要的,由于抽象是理论,依赖是实践。抽象是 app 的逻辑布局,依赖关系是(组件)如何实际组合在一块儿。在 Clean Architecture 中,这二者是匹配(译者注:指方向一致)的。而在标准三层架构中则否则,若是你不当心,很容易致使各类逻辑上的不一致和混乱。

III.层与层之间的通讯

如今咱们将 app 分模块,将全部内容分开,将业务逻辑放在咱们 app 的中心,并在外层实现细节,一切看起来都很棒。 可是你可能很快遇到一个有趣的问题。

若是你的 UI 是一个实现细节,网络是一个实现细节,业务逻辑在中间,那么咱们如何从互联网获取数据,通过业务逻辑,而后发送到界面?

业务逻辑在中间,应该协调网络和界面,但它甚至不知道二者的存在。这是一个关于通讯和数据流的问题。

咱们但愿数据可以从外层流向内层,反之亦然,但依赖规则不容许。 让咱们举个最简单的例子。

咱们只有两层,绿色和红色的。绿色的是外层,它知道红色的,红色的是内层,它只知道本身。咱们但愿数据从绿色流向红色,而后折回绿色。该解决方案先前已经暗示过了,看下图:

图的右边部分显示了数据流。数据源于 Controller,通过 UseCase(或者替换成你选择的组件)的输入端口,而后经过 UseCase 自己,最后经过 UseCase 输出端口发送到 Presenter。

图的主要部分(左边)的箭头表示组合和继承 —— 组合用实心箭头表示,继承用空心箭头表示。组合也被称做 has-a 关系,继承被称做 is-a 关系。圆圈中的 “I” 和 “O” 表示输入和输出端口。能够看到,定义在绿色层中的 Controller,拥有一个(has-a)定义在红色层中的输入端口。UseCase(齿轮,业务逻辑,如今不重要)是一个(is-a)(或实现)输入端口,而且拥有一个(has-a)输出端口。最后,定义在绿色层中的 Presenter 其实是一个(is-a)定义在红色层的输出端口。

如今,咱们能够将其与数据流匹配。Controller 拥有一个输入端口 —— 拥有一个指向它的引用。它调用输入端口的一个方法,这样数据就从 Controller 流到输入端口。但输入端口是一个接口,而它的实际实现是 UseCase。也就是说,它调用 UseCase 的一个方法,这样数据就流向了 UseCase。UseCase 执行某些操做,并但愿将数据发送回来。它拥有输出端口的一个引用 —— 输出端口定义在同一层 —— 所以它能够调用上面的方法。所以,数据流向输出端口。最后 Presenter 是,或者实现了输出端口,这是魔法的一部分。由于它实现了输出端口,数据实际上流到它那了。

巧妙的是,UseCase 只知道它的输出端口,世界在此中止(意指数据流到此结束)。Presenter 实现了它(输出端口),实际上它能够被任何对象实现,由于 UseCase 不知道或不关心这些,它只清楚其层内的一亩三分地。能够看到,经过结合组合和继承,咱们可使数据流向两个方向,尽管内层并不知道它们在和外部世界通讯。瞄一眼下图:

能够看到,和依赖箭头同样,has-a 和 is-a 箭头也指向中间。这是符合逻辑的。根据依赖规则,这是惟一可行的方法。外层能够看到内层,但不能反过来。惟一复杂的部分是,is-a 关系尽管指向了中间,却反转了数据流。

请注意,定义输入和输出端口是内层本身的职责,所以外层可使用它们与其创建通讯。我说过,这个解决方案先前已经暗示过,并且已经有了。那个讲解抽象的通知例子,也是这种通讯的一个例子。咱们在内层定义了一个通知接口,业务逻辑能够用来向用户显示通知,可是咱们在外层也定义一个实现。在这种状况下,通知接口是业务逻辑的输出端口,用来和外部世界(在本例中,就是和具体的实现)通讯。你不须要把你的类命名为 FooOutputPort 或者 BarInputPort,咱们命名端口只是为了解释理论。

总结

那么,它是过分复杂,过分费解的过分工程吗?好吧,当你习惯了,它就简单。而且这是必要的。它容许咱们使得好的抽象/依赖实际匹配真实世界的通讯和工做。也许这一切都提醒你不过是空中楼阁:美丽,理论上优雅,但过于复杂,咱们仍然不知它是否有效,但在咱们的案例中,它确实有效。

这就是本系列的第二部分。最后,第三部分,毕竟咱们已经了解了理论和架构,将讲解全部你须要了解的那些图上的标签。换句话说,分离的组件。咱们将向你展现一个真实的应用于 Android 的 Clean Architecture。

原文

相关文章
相关标签/搜索