在系列的第一部分中,咱们介绍了咱们在寻找可行的体系结构方面所犯的错误。 在这一部分中,咱们将介绍所谓的Clean Architecture.android
你在google“clean architecture”中遇到的第一个图像是这样的:数据库
它也被称为洋葱架构,由于图表看起来像一个洋葱(当你意识到你须要写多少样板时,它会让你哭泣); 或端口和适配器,由于您能够看到右下角有一些端口。 六角形架构是另外一种相似的架构。架构
Clean architecture是前面提到的Bob叔叔的心血结晶,他也写了关于Clean Code和Clean Coder的书籍。 这种方法的主要观点是业务逻辑(也称为domain)处于宇宙的中心。app
当你打开你的项目,你应该已经知道这个APP是什么。 其余一切都是实现细节。 例如:持久性 - 这是一个细节。 定义一个接口,使内存实现一个快速而肮脏的实现,在业务完成以前不要考虑它。 而后,您能够决定如何确实坚持数据。 数据库,互联网,组合,文件系统 - 也许把它们留在内存中,也许事实证实你根本没必要坚持它们。 在一句话中:内层包含业务逻辑,外层包含实现细节。dom
这就是说,有一些Clean Architecture能够实现这些功能:布局
依赖关系规则能够用下图来解释:google
外层应该取决于内层。 红色方块中的这三个箭头表示相关性。 “依赖”也许这是最好用的术语,如“看”,“知道”,或者“知道的。”没那么好。在这方面,外层看到并了解内层,但内层既不看也不知道外层。 如前所述,内层包含业务逻辑,外层包含实现细节。 结合依赖关系规则,业务逻辑既看不到,也不知道实现细节。 这正是咱们正在努力完成的。spa
您如何实现依赖关系规则取决于您。 您能够将它放在不一样的包中,但要当心不要使用“内部”包装中的“外层”包装。 可是,若是有人没有意识到依赖性原则,没有什么能阻止他们打破它。 例如,更好的方法是将图层分红不一样的Android模块,并调整构建文件中的依赖关系,以便内层没法使用外层。 在Five,咱们使用了二者之间的东西。blog
值得一提的是,尽管没有人能够阻止你跳过图层 ,例如,在蓝色图层组件中使用红色图层组件。我强烈建议您仅访问图层旁边的组件。继承
抽象原则已经暗示过。 当你走向图的中间时,东西变得更加抽象。 这是有道理的:正如咱们所说的内圈包含业务逻辑,外圈包含实现细节。
您甚至能够在多个图层之间划分相同的逻辑组件,如上图所示: 在内层中能够定义更抽象的部分,外层中更具体的部分。
一个例子会说清楚:咱们能够将抽象接口定义为“通知”并将其放入内层,这样,您的业务逻辑就可使用它来向用户显示通知。 另外一方面,咱们能够经过实现使用Android通知管理器显示通知的方式来实现该接口,而后将该实现放入外层。
经过这种方式,业务逻辑能够在咱们的示例中使用功能通知 - 但它不知道实现细节的任何内容:实际通知是如何实现的。 并且,业务逻辑甚至不知道实现细节存在。 看看下面的图片:
将抽象与依赖规则结合起来时,事实证实,使用通知的抽象业务逻辑既没有看到也没有意识到使用Android通知管理器的具体实现。 这很好,由于咱们能够切换具体实现,业务逻辑甚至不会注意到它。
让咱们简单地比较一下使用标准三层体系结构时抽象和依赖关系的外观和工做方式。
您能够在图中看到标准三层体系结构中的全部依赖关系都转到数据库。 这意味着抽象和依赖不匹配。 从逻辑上讲,业务层应该是应用程序的中心,但不是,由于依赖关系会转向数据库。
业务层不该该知道数据库。 而在清洁架构中,依赖关系转到业务层(内层),而抽象层也向业务层上升,所以它们匹配得很好。
这很重要,由于抽象是理论,依赖是实践。 抽象是应用程序的逻辑布局,依赖关系是它如何组合在一块儿。 在 clean architecture中,这二者匹配,而在标准的三层架构中则没有; 若是你不当心,这可能会很快致使各类逻辑不一致和混乱。
如今咱们已经将应用程序划分为模块,很好地分离了全部内容,将业务逻辑放在应用程序的中心和郊区的实现细节中,一切看起来都很棒。 可是你可能很快遇到了一个有趣的问题。
若是你的UI是一个实现细节,那么internet就是一个实现细节,业务逻辑介于二者之间,咱们如何从internet获取数据,经过业务逻辑传递它,而后将其发送到屏幕?
业务逻辑处于中间,应该在互联网和用户界面之间进行调解,但它甚至不知道这两我的存在。 这是一个沟通和数据流的问题。
咱们但愿数据可以从外层流向内层,反之亦然,但依赖性规则不容许这样作。 让咱们将其简化为最简单的例子:
咱们只有两层,绿色和红色。 绿色的是外在的,知道红色的,红色的是内在的,只知道本身。 咱们但愿数据从绿色流向红色流,而后返回绿色流。 该解决方案以前已经被暗示过,以下图所示:
右下角的图部分显示了数据流。 数据从Controller经过Use Case(或用用户选择的组件替换用例)输入端口,而后经过Use Case自己,而后经过Use Case输出端口返回给Presenter。
图的主要部分上的箭头表示组成和继承 - 组成和继承 - 组成由一个实心箭头表示,继承由一个一个空箭头表示。 组成也被称为有关系,而继承是一种关系。 圆圈中的“I”和“O”表示输入和输出端口。 能够看出,在绿色层中定义的控制器具备在红色层中定义的输入端口。 Use Case(齿轮,商业逻辑,如今不重要)是(或实现)输入端口并具备输出端口。 最后,在绿色层中定义的Presenter其实是在红色层中定义的输出端口。
咱们如今能够将其与数据流相匹配。 Controller有一个输入端口 - 它实际上有一个参考。 它调用一个方法,以便数据从Controller传输到输入端口。 可是输入端口是一个接口,实际的实现就是Use Case:因此它在一个Use Case上调用一个方法,数据流向Use Case。 Use Case作了一些事情,并但愿将数据返回。 它有一个对输出端口的引用 - 由于输出端口是在同一层定义的 - 因此它能够调用它的方法。 所以,数据进入输出端口。 最后,Presenter是或实现输出端口; 这是神奇的一部分。 因为它实现了输出端口,数据实际上流入了它。
诀窍是Use Case只知道它的输出端口; 世界在这个输出端口结束。 实施它取决于Presenter, 它可能已经被任何东西实现了,由于用例不知道或关心,而且只知道其层中的小世界。 咱们能够看到,经过组合和继承的组合,咱们可使数据在两个方向上流动,尽管内层不知道他们正在与外部世界进行通讯。 快速浏览下图:
你能够看到两个箭头都指向中间,就像依赖箭头同样。 那么,这是合乎逻辑的。 根据依赖规则,这是惟一可行的方法。 外层能够看到内层,但不是其余方式。 惟一棘手的部分是,这是一种关系,虽然它指向中间,但会颠倒数据流。
请注意,定义其输入和输出端口是内层的责任,以便外层可使用它们与之创建通讯。 我已经说过这个解决方案已经在以前被暗示了,并且已经有了。 说明抽象的通知示例也是这种通讯的一个例子。 咱们在内层中定义了一个通知接口,业务逻辑可使用它向用户显示通知,但咱们也有一个在外层中定义的实现。 在这种状况下,通知接口是业务逻辑的输出端口,它用于与外部世界进行通讯 - 本例中的具体实现。 您没必要为您的类命名FooOutputPort或BarInputPort; 咱们命名只是为了解释这个理论。
那么,这是过于复杂,过分模糊的过分工程? 好吧,当你习惯它时很简单。 这是必要的。 它使咱们可以在现实世界中实现良好的抽象/依赖匹配实际交流和工做。 也许这一切都让人联想到弦理论:理论上美观,可是过于复杂,咱们仍然不知道它是否有效,但在咱们的案例中 - 它确实有效。:)
这就是这个系列的第二部分。 最后,第三部分,毕竟咱们已经了解了理论和体系结构,将涵盖全部你须要了解的关于这些图表上的标签,或者换句话说 - 单独的组件。 咱们将向您展现在Android上应用的真实生活Clean Architecture。
这是Android体系结构博客文章系列的一部分。您还能够查看其余部分:
Part 5: How to Test Clean Architecture
Part 4: Applying Clean Architecture on Android (Hands-on)