我们如何实现RIBs体系结构。 报告Yandex.Taxi

嗨,我叫Alexei Valyakin,我正在为Android编写应用程序。 几个月前,我在Yandex.Taxi团队与移动开发人员的会议上发表了讲话。 我的报告专门介绍了如何在Taxis中向RIB架构过渡(RIB代表排名前三的路由器,交互器,构建器)。 这是视频,剪辑后,摘录如下:


-是时候大肆宣传火车了。 这是有关Android体系结构的经典主题。

认真地说,今天我想向您介绍我们如何以及为什么实现此体系结构,遇到了哪些困难以及它如何为您提供帮助。



当我加入公司时,我们的团队由四人组成。 那时我们已经遇到了许多困难。 该项目很旧,于2012年开始。 那里收集了很多技术问题,其中一个是错误构建的CI,方法的差异很大,测试无法涵盖所有​​内容。 通常,存在大量的困难和合并冲突。

在两年内,我们已经发展到12个人,这意味着我们已经增加了功能开发的并行性。 因此,存在更多的合并冲突,并且由于代码的一致性,您理解了这可能导致的结果。 在某个时候,我们刚刚开始下沉,以某种方式我们不得不弄清楚。 这些问题的一部分通过点重构解决,部分通过组件库解决,值得在单独的报告中讨论。



所有开发人员都想要什么? 优美的架构,开发灵活性,易于添加的功能,以及当然减少了合并的复杂性-因为从根本上说,当隔离的功能经过测试并可以正常工作时,它们会导致一些错误,这些错误可能会在发布阶段出现。 当他们掌握并发布时-跳,一切都崩溃了。 这是我们想要了解的示例图片。



你怎么去找她 显然,有很多关于如何做某事的选择。 我将讨论主要方法及其缺点。 当然,还有其他解决方案。



经典MVP。 经典MVP面临什么挑战? 如果我们看一下我们的项目示例,就会发现有MVP活动,MVP片段和MVP视图。 事实证明,需要添加的内容存在很大的可变性。 在某些情况下,您认为需要添加一个视图和一个片段。 事实证明,添加管理器附带的一些小功能非常困难,因为它通常位于单独的MVP活动中。

MVP的第二个问题与路由器的请求有关。 您想灵活地连接孩子,以便对此具有某种本质。 因此,通常MVP会来到某种自制路由器或其他路由器上。 而视图驱动方法是一个很大的缺点。 在很多MVP模式中,是将主持人注入到视图中,这已经使它减少了被动性,并违反了干净的体系结构。



毒蛇更好。 他具有诸如路由器之类的实体,他更加抽象,但是他有许多缺点。 它仍然具有视图驱动逻辑,它具有业务逻辑通过的必需的presenter层,但这并不总是正确的。 View层也是必需的,您不能摆脱它。

主要问题是这种体系结构来自iOS世界,因此需要以某种方式针对Android进行调整。 我看到有一些改编,其中有些甚至没有,但也有缺点。



显然,在建筑世界中没有万灵药,每种建筑都有其优缺点。 RIB也有缺点。 通常,Uber在概念级别上大部分引入了这种架构。 他们有很多开放类,没有复杂的例子。 您可以阅读一些简单的教程。 而且,当切换到任何体系结构时,都需要进行大量的重构,而不仅仅是RIB有这个重构。



RIBs体系结构由什么组成? 她使用Dagger组件。 她的主要班级Builder汇集了整个组件,其中包括以下部分:路由器,交互器。 演示者(视图)-一个单独的层,有时可能存在,有时不存在。 同时,Presenter(View)可以合并为一个类,或者如果您具有错误的呈现逻辑,则可以将其拆分。

还有什么好玩的? 由于Presenter(视图)是可选的,因此您可以以与新业务功能几乎相同的方式添加新屏幕。 您的结构更加整洁易懂。 孩子对父母一无所知,而父母也对孩子有所了解。 让我们看看这看起来像一个简化结构的例子。



您总是有某种根源。 这是根RIB。 它根据应用程序的状态决定其本身包括什么:它是授权状态还是未授权状态。 让我们看一下我们的应用程序示例。 也许您在订单上或不在订单上。

例如,另一个很酷的RIB功能。 您可以将RIB创建为模式屏幕,然后原则上可以从任何RIB进行连接。 由于RIB对父级一无所知,因此任何父级都可以提供子级RIB必需的依赖项。



模块的结构可能看起来像这样。 目前,我们只是在考虑将应用程序分解为模块。 他和我们在一起。 实际上,一切都非常经典地实现。 您有某种通用模块,可以根据需要将其分解为更小的模块。 您具有某种核心API,可能是网络,数据库等。在我们的坐标系统中,特定的RIB是一个单独的模块,它包括所有Common等,它需要什么,包括子RIB。

如果某些事情需要在多个RIB之间进行组合,则有些具有共享要素类的示例仅在单独的模块中脱颖而出。



RIB的优点是什么? 易于测试,高度的代码隔离,单一活动的方法,零碎的片段(无论谁都能理解)和统一性。 这是一个跨平台的体系结构,iOS和Android都有一种方法。 如果您有两个团队,这将是一大优势,因为他们会说相同的语言。

这是重要的一点。 是否想要一些有关实施RIB的生活小技巧? 假设您将依赖关系转移给自己,然后开始添加继承人的扩展功能,并且了解所有这些对您来说还不够,您需要对其进行调整。 最后,您只需将它们转移到您的班级上即可。 还有另一种方式-当您立即将它们转移到您的班级上时,而不会浪费时间选择第一个选项,并对其进行调整。

现在该看看代码及其外观了。



它们有一个方便的插件,可让您生成RIB所需的类,而不会浪费时间创建它们。 他创建了四个主要类-Builder,Interactor,Router和View,我将在以下幻灯片中对其进行详细介绍。 它还会生成测试。 当然,他不会为您编写它们,而您将必须自己编写它们,但是,这非常不错。 现在,我们正在考虑创建一个插件,该插件将简化带有RIB的新模块的创建。 该插件将立即连接所有必需的依赖项,并且将花费更少的时间来配置模块。



因此,Builder是经典的粘合代码组件,其主要任务是将所有组件组装在一起,组装Dagger组件和View。 通常,View将只调用构造函数,那里没有什么复杂的。 在某些情况下,它可能会膨胀。



第二部分在Builder中,是关于成瘾的,即关于孩子如何从外部获得任何成瘾的信息。



它具有定义其所需依赖项的父组件接口。 因此,在子组件的生成器中,提供了它从上面需要的所有依赖项。



本质上,Interactor是业务逻辑中最重要的一类。 仅允许进样。 实际上,这是正在测试的最重要的内容。 它使用流RX事件从UI层接收事件。 Presenter是定义我的事件提供的方法的接口。

对于RIB,还有什么方便之处? 通过在Interactor和Presenter层上,您可以组织自己喜欢的交互。 它可以是MVP,MVVM和MVVI。 在这里,每个人都可以自由选择自己喜欢的东西。 订阅Presenter事件可能看起来像这样。



这是这些事件的处理方式。



路由器-负责连接子级的类。 他没有业务逻辑;他本人也没有促使孩子建立联系。 这使Interactor具有了这样的概念。 本质上,在这里我给出一个简化的例子。 实际上,Builder只是调用Build方法,该方法收集子RIB,并使用attach子代直接添加该子代,并添加一个视图。 通常,此逻辑可以封装在单独的过渡中,您可以配置动画-所有这些都取决于您的需求。



在此体系结构中,视图尽可能被动。 她没有向自己注入任何东西,她几乎一无所知。 在最简单的情况下,如果您毫不费力地演示它,就可以实现Presenter接口。 在更复杂的情况下,此逻辑分为两类。 也就是说,您有一个单独的Presenter类,该类仅映射业务数据-例如在模型视图中。

这是Interactor如何接收UI事件的示例。 看一下Rx流。



您不能只是建立一个新的架构。 当您执行此操作时,尤其是在大型项目中,就会遇到某些困难。 您需要了解我们有一个庞大的项目:大约20个Activity(如果不是更多的话)和大约60个片段。 所有这些逻辑都非常分散。 有必要将所有这些合并在一起。



首先,您需要将所有内容合并到一个导航点中,首先要创建一个上帝对象-特定的活动路由器,在其中您还将管理一堆片段,因为您将拥有许多旧代码。 没有人会让您整天实施新架构并停止业务。 为此,您需要与一堆RIB交朋友。 RIB自然也有一个堆栈-可以从引擎盖下对其进行访问。 但是这里重要的是什么? 我们必须自己完成很多代码。 Uber不支持屏幕旋转,因此它并没有太多恢复状态。 因此,当我开始研究此体系结构时,我要做的第一件事就是在路由器上添加继承人,以支持还原RIB的层次结构和应用程序的整个状态。

您将需要支持功能切换。 没有一个大型项目就不能没有它。 现在,我们的一名开发人员正在开发一个概念。 如果有人观看了Mobius 2016,我们在其中谈论了Plugin Factory,它使您可以动态连接和断开某些逻辑块-不一定是屏幕碎片。 例如,它可以根据来自服务器的实验来起作用。 一切都抽象地完成,并且交互也得到了简化。

RIB的工作流程也是您可能需要的一个有趣的概念。 在这种情况下,您有几个彼此完全不了解的RIB,它们的级别大致相同,但是同时您需要使用输入端的数据来启动该过程,最后必须将所有内容放在一起。

并且,例如,模态屏幕。 我们采用了超级定制设计,因此几乎没有经典的对话框。 一切都是自写的,我们必须自己实现一切。

使用RIB可以得到什么? 隔离代码,易于理解的简单体系结构,简便的模块化方法,消除碎片,单一活动方法以及功能并行开发的便利性。

参考文献:
-github.com/uber/RIBs
-github.com/uber/RIBs/tree/master/android/tutorials
-habr.com/zh/company/livetyping/blog/320452
-youtu.be/Q5cTT0M0YXg
-github.com/xzaleksey/Role-Playing-System-V2
-github.com/xzaleksey/DeezerSample

最后一个链接指向我的宠物项目。 六个月前,我开始在RIB上进行开发,以在将此架构引入我们之前尝试该架构。 并且有或多或少的实际案例可以为您提供帮助。 我做了很多实验,所以有争议。 但总的来说,您可以看到它的工作原理。 也许从那里您会为自己准备一些东西。

我们还考虑将所有这些分配到一个单独的库中,就像我们使用组件库时所做的那样。 会有这样的Yandex肋骨。 但这是将来的事情。 我告诉了你一切,谢谢。

Source: https://habr.com/ru/post/zh-CN467523/


All Articles