iOS上的Cleaner Architecture

今天,我不会教您任何新手或突破性的东西。 相反,我只是想提醒您一些您已经知道的事情:单一责任原则(SRP)。 更具体地说,我想讨论如何在干净的体系结构中正确使用它,并且假定您已经对它有所了解(如果不是,我建议您阅读下面的资源,然后再回来)。 因此,请不要忘记提醒自己在制定决策时明确考虑SRP,并希望这将有助于我们设计更好的软件!

什么是SRP?

这里是一个定义[1]:“单一责任原则是一种计算机编程原则,它指出每个模块或类都应对软件提供的功能的一部分负责,而责任应由类完全封装。 其所有服务应严格地与这一责任保持一致”。

一个班级只有一个改变的理由。 这样做将使更改变得更容易,减少耦合,提高可测试性,加快开发速度等等! SRP也是干净架构的基本思想(但是,它当然也适用于其他方法,例如MVC,MVVM,反应式等)。

为什么不使用MVC?

明显的答案是:因为Massive View Controller。 这当然是个玩笑–但很有趣,因为它是真的。 几乎可以问一个问题:当MVC导致大量问题(例如大规模视图控制器)时,为什么MVC是iOS上的默认体系结构? 答案是,这首先不是问题。 如果控制器很大,那不是体系结构的问题,而是程序员没有正确使用它。 您可以使用MVC编写一个非常干净的应用程序,而大型视图控制器问题可以通过以下方式轻松解决:

  1. 在一个场景中不只使用一个控制器,
  2. 将工作委托给工人/服务班级。

换句话说,通过应用SRP。 那么,如果不是MVC,那么为什么要使用干净的体系结构呢? 如果在iOS上使用的MVC出现问题,则说明它相当模糊,并且将许多决策留给了程序员。 控制器的责任是什么? 如果您不小心,它将获得太多。 但是,我们将所需的所有其他责任放在哪里? 在模型中? 我们该如何构造? 该架构并没有真正告诉我们任何这些。 我们靠自己,这意味着有很多机会引入错误!

如果您不想考虑所有这些,则可以使用简洁的体系结构。 干净的体系结构明确地在其类之间划分了一些职责:演示者弥合了UI和业务逻辑之间的鸿沟,交互者处理了我们的用例,路由器帮助我们进入了新的场景,等等。职责很明确,我们的代码库更加简洁。

那么,仅通过使用干净的体系结构,我们是否可以解决问题? 现在我们的代码库中有SRP吗? 好吧,不一定。 架构是很有帮助的,但不能解决我们所有的问题。 我们仍然需要思考,做出选择,并付出一些努力使事情变得更加干净。

MIP

干净的架构在Apple平台上已变得非常流行,这是有充分的理由的。 我们甚至可以选择几种方法,例如VIPER [4]和Clean Swift [3]。 让我们看一些使用Clean Swift(或我喜欢称之为CS的真实项目),它与我们在iOS上使用的体系结构的命名约定一致。

我见过带有大型和复杂交互器的代码库,这些代码库显然不遵循SRP。 我称其为巨大的交互器问题(MIP)。 MIP可能不如大型视图控制器那么糟糕,因为交互器并不关心UI,但它们仍然尝试做太多事情。 如果程序员能够编写大规模的视图控制器,那么他当然也会编写出色的大规模交互器。 问题是,即使我们认为我们使用的是干净的体系结构,责任也没有得到适当的分离,因此没有尽其所能。 为避免MVC / MIP问题的根本原因,我们将无情地应用SRP。

交互器包含应用程序业务逻辑,但是每个视图控制器只有一个交互器。 对于小型场景而言,这很好,但是除非您将其与我之前提到的“每个场景不使用一个视图控制器”建议结合使用,否则它可能很快就会失控。 我认为,在复杂的场景中,交互器不应真正做任何有趣的事情。 无需算法,无需数据库或解析,也不需要嵌套的if语句或循环。 这属于Worker类。 交互者的职责是将工作委派给其工作人员,并将结果传递给演示者。 CS [3]的原始教程仅包含一小部分有关工人的内容,由于篇幅短,其重要性可能并不那么明显。 当然,这是有道理的,因为工作人员的责任是特定于您的应用程序的,并且包含其业务逻辑,因此本教程实际上没有太多补充。 尽管如此,我认为应该更加强调工人的重要性。

在干净的体系结构中,交互器应代表一个用例。 这意味着单一责任应该是一个阶级的唯一关切。 因为CS每个场景只有一个交互器,而一个场景通常包含多个用例,所以交互器将承担许多责任。 我们可以选择忽略这一点,当事情变得简单时,这是可以接受的,但是随着交互器的增长,它将变得越来越难以处理。 另一种选择是将这些责任委托给工人。 在此设置中,交互器将创建多个工作程序并仅指导他们。 因此,如果请求来自输入,则交互器将简单地将其委派给工作人员,然后将结果传递给演示者。 听起来似乎太简单了,但是对MIP来说是一个很大的改进。 工作人员每个都处理一个用例,他们听起来比交互者更像是一个交互者,不是吗?

那VIPER呢?

我听到很多人说VIPER非常复杂。 设置时间太长,有太多的小类,请考虑新开发者! VIPER是SRP的一个很好的例子,因为VIPER非常重视SRP。 如果您也很重视SRP,并尽最大努力将其应用于您的MVC项目,您可能会得到类似的东西(实际上发生在我身上。我以为我发明了轮子!只是发现它是已经存在很长时间了…)。 不过,您不会免费获得详细的文档。

毕竟,Clean Swift和VIPER非常相似,它们都是基于干净的体系结构的。 主要区别在于VIP周期以及每个场景中的交互者数量。 但是,如果我们严格地将SRP应用于CS,划分CS交互器的职责并为每个用例创建一个工作人员类,则基本上可以得到VIPER(如果我们忽略一些细节,CS工作人员将对应于VIPER交互器,而CS的Presenter-与VIPER演示者的交互器对)。 实际上,由于VIP周期,CS变得更加复杂。 是的,您没听错,Clean Swift比VIPER还要复杂! (至少对于想要避免使用MIP并正确应用SRP等的大型项目而言。)VIPER并不像您认为的那样复杂。 因此,如果您还没有,请不要害怕学习它所提供的功能。 即使您最终没有在下一个项目中使用它,理解它并理解“为什么”以它的方式做事也是您工具箱中的另一种工具。 使用它可以使您自己和他人的生活更加轻松。

有关SRP的更多信息

以下是一些建议:

  • 不要混合抽象层。 在高级业务逻辑中间进行低级计算是违反SRP的标志。 毕竟,在处理组织或器官时,我们不想处理细胞的复杂性,反之亦然。
  • 对班级的责任明确。 将其写在标题上方的文档注释中。 描述应简明扼要,同时包含所有责任。 但是请注意“经理”之类的词。 无论如何,经理做什么? 它太含糊,可以在不通知您的情况下轻松承担多项职责。 对类进行更改时,请确保它仍然正确,否则请进行重构。 如果您认为自己没有时间这样做,那么不用担心,从长远来看,这将节省时间,因为您将减少技术债务并保持项目的清洁度。
  • SRP可以应用于多个级别,从一般体系结构到单个方法。 如果一个方法执行多项任务,则将其内容拆分为新的方法,这些方法将执行部分任务并从原始方法中调用这些任务。 请记住,一种方法只应占据一个抽象层。
  • 大量的行数表明SRP已损坏。 如果您注意到滚动了很长时间,则可能是有问题。 SRP是比行数更好的指标,因此要列出职责,而不是发明一个无法超越的不可思议的数字。 如果不止一个,请将其拆分。 较短的类和方法也更易于维护,因此请尝试使内容保持尽可能短(但不要短)。
  • 务实。 与其完全遵循规范和指南,不如了解为什么他们做出设计选择以及实现目标,这会更有帮助。 然后做出明智的决定(只是不要把懒惰和借口伪装成实用主义,因为它们不是!)

总之,请勿编写大型控制器。 明确考虑职责,并确保您在一个地方没有太多(理想情况下,太多意味着两个)。 不要只是在类中添加更多代码,因为如果它与自己的职责相矛盾,那么现在就更容易了。 从长远来看,坚持SRP很有可能节省时间-因此请不要找借口。 努力编写更简洁,实用的代码可以带来除更好的软件以外的其他功能吗?

资料来源

[1] https://en.wikipedia.org/wiki/Single_responsibility_principle

[2] https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

[3] https://clean-swift.com/clean-swift-ios-architecture

[4] https://objc.io/issues/13-architecture/viper