抑制当务之急

面向对象的范例对于企业而言极为方便:它使您可以实施几乎任何想法,并提供可接受的产品性能。 在这种情况下,就产品而言,我们指的是一个iOS应用程序,因此,总而言之,我们将从该平台上的开发着手。

对此流行范例的众所周知的缺点视而不见,其缺点包括最重要的优势-开发的灵活性。 为什么这是负号? 显而易见,灵活性除了具有解决业务问题的基本能力外,还可以通过多种方式来实现。 的确,尽管在任何情况下都可以正确解决业务任务,但对于一种正确的方法还是有许多错误的做法,但是在实现上存在差异,其可扩展性和透明性已经取决于所采用方法的正确性。

考虑到墨菲定律,得出的结论是,在没有适当的体系结构限制的情况下,它更有可能遵循混乱的道路,也就是说,代码的质量将迅速下降,仅是因为范式允许它。 本文将讨论可能的架构限制之一,这将有助于保持善恶力量的平衡,换句话说,就是项目代码库中熵增长的动态。 重要的是,这种动态关系与编写代码所涉及的人数直接相关,因此,对于大型项目,所选限制的正确性尤其重要。 有什么意义?

重点很简单。 让我们从下一个公理开始吧-对象中的属性越多,它就越“糟糕”。 这种说法可以解释如下:随着内部状态数量的增加,对象的所有积极指标(可扩展性,模块性,透明性,可测试性)都会降低。 一个人可以反对,说对象的复杂性及其功能是相互联系的,没有第一个对象,第二个对象就不会发生。 一切都是正确的,但是,遵循“有状态”的道路,复杂度的增长呈指数增长,尽管事实上理想情况下增长应该是线性的,或更简单地说,新功能不应使现有功能复杂化。

PS在这里需要澄清的是,功能被理解为与业务逻辑在语义上相关的层,因此通常会决定使现有的类复杂化,而不是创建一个新的类。 在这种情况下,很难找到与第一个SOLID原则的矛盾之处。

一个示例是一个完全标准的屏幕,其中包含可供选择的实体列表。 某些属性可能会变得不稳定,但是IB将我们绑定到控制器的默认构造函数,这迫使我们“制造污垢”。 可以通过同一文件中的每个类方法隐式获得对这些属性的访问。 最令人不愉快的时刻是它们的可变性也没有被任何东西覆盖,这以某种方式导致不可逆转的后果,即紧密的联系,因此,导致某些缺陷的修复导致新缺陷的出现:



我们可以得出结论,具有共同状态的对象的复杂度线性增加实际上是无法实现的。 这种方法的功能变得单一,难以进行任何形式的隔离。 一个很好的例子是反模式Massive-View-Controller,它很常见,并且清楚地表明了对项目没有任何限制的结果。



以UIKit为例,您可以确切地看到命令式开发样式如何影响代码的复杂性,以及框架在什么时候“强制”要在类中创建属性。
最简单的情况-按下按钮的处理-通常仅通过定义“脏方法”(即,通过无法接收任何有用信息的函数)来执行,因此您将不得不使用这些属性:



因此,按钮的“功能”将使对象的其余功能变得复杂,因为该类的所有居民都可以访问该数据。 实际上,几乎所有的iOS UI控件都以类似的方式工作。 因此,我想到在UI元素上实现某种包装,例如,将一个闭包作为对特定元素的“操作”的处理,但是困难在于如何使这种包装简洁而可靠。 毕竟,问题不仅存在于信息的输入中,而且还存在于信息的输出中,例如,带有数据的普遍存在的表也“脏”了,并且不知道所显示的数据,因此必须将其保存在对象中:



方便吗 方便地,每个人总是可以访问数据。 灵活,快速,必要。 但是,通常,随着时间的流逝,所有这些变成了一千行代码的具体方尖碑,以及那些承担着从事此类工作的人们的眼泪。

返回到这些限制,可以从上面的内容中形成以下原理-在类中没有属性的代码更加简洁,在支持和扩展的情况下更有利可图。 理想情况下,对象的逻辑应采用其“纯”方法,该方法将其所有依赖性作为输入,并在输出中具有其活动的结果。
想法是将对象的私有状态降低一级。 也就是说,如果私有人员关闭了外界的财产,那么我们的任务就是走得更远,并在方法本身的水平上加强封装。 乍看之下,对于平台的所有异步特性和主框架的强制性,似乎整个想法不仅是不可能的,而且至少其实现会感到不自然。 而且,很可能是这样,但这是可以通过引入附加级别的抽象来解决的那些问题之一。

如果我们想让类的所有逻辑适合其方法而又不求助于属性,则需要与闭包紧密合作。 IOS具有用于管理异步操作的标准工具,例如GCD和OperationQueue。 看起来它们足够了,但是当您尝试将这个想法付诸实践时,一切都会变得远没有您想要的那么乐观。 通过这种方法,除了有很大的机会获得回调地狱的事实之外,代码本身也变得很麻烦,有很多逻辑漏洞并且将紧密地联系在一起。 这样的代码可能会比我们试图如此迅速地逃脱的代码更加复杂。

如果环顾四周,您会发现实现这一目标的方法更加美观实用。 反应式编程长期以来一直用于商业软件开发中,是异步前端世界的理想选择。 在这种情况下,我们将考虑为Swift-Rx实现反应式范例的一种(相当成功的)实现。



它提供了一个称为Observable的简单实体。 这是对事件流的一种抽象,可以订阅它,此后,订阅者将随着时间的推移接收这些事件:



想象事件流的最简单方法是显示一个常规按钮。 它的操作事件在这里是一个流,因此任何对象都可以订阅和接收其单击事件,最重要的是,按钮对自己的订阅者一无所知。 方便地,几乎所有动作都可以转换为相似的值序列,这很重要,因为Observable可以彼此组合,因为没有标准框架可以实现。

例如,您可以通过按下按钮(双击过滤)来发送多个请求,等待它们中的每一个回答,然后将答案与用户在屏幕上输入的内容结合起来,执行另一个请求并转到下一个屏幕,将结果传输到该屏幕上。退出屏幕时,此Rx可以简洁地处理错误并完成此链(取消请求),所有这些逻辑将需要两打输入的代码行:



值得一提的是disposeBag的唯一属性。 从屏幕截图可以看出,每个订阅都放置在其中,这对于控制其寿命是必需的。 也就是说,只要控制器放置在“袋”中,订阅就一直有效。

除了紧凑之外,在上面的代码中也很难出错,因为每个闭包都返回一些东西并且不包含副作用。 这就是我们一直在寻找的力量。

您会注意到一个重要的观点:由于该类没有属性,因此不必编写[弱自我],这会积极影响代码的可读性。 所有函数都可以并且最好在使用它们的方法中本地定义,或者在单独的类中取出。 顺便说一下,在这种情况下,到依赖项的链接(ViewModel,Presenter等)可以作为参数传递给控制器​​方法,在这种情况下,无需将其保存在属性中。 这是正确的。

复习之后,是时候使用Observable来简化开发了。 到底如何 让我们回到“干净”方法的思想,尝试用唯一的方法实现小屏幕的逻辑,为清楚起见,我们将选择结束视图加载的方法(viewDidLoad)。 自然地,如果屏幕是用IB组成的,那么我们将不得不为商店创建属性,但这并不可怕,因为元素本身并不代表业务逻辑,因此它们不会极大地影响屏幕的复杂性。 但是,如果屏幕是由代码组成的,那么您可以完全不使用属性(disposeBag除外),在我们的方法中创建元素并在那里使用它们。 前面介绍的UIKit元素的必要性如何? Rx除了方法本身之外,还为标准UI组件提供了反应式包装,因此在大多数情况下,您可以当场获得必要的事件顺序。 或者相反,将现有的Observable绑定到例如一个表-向其提出请求,以便它在完成后立即更新其内容:



绑定到集合非常灵活,但是默认情况下,它仅通过reloadData起作用。 对于点更新,有一个来自同一作者的精彩调试库-RxDataSources。 有了它,您就可以省去batchUpdates期间的崩溃。

接下来会发生什么? 单屏方法将会发展,并且在相当复杂的情况下,将变得难以维护。 当发生这种情况时,突然变得很清楚,该方法不依赖于自身,而被动方法将代码划分为逻辑块,通过以类似方式设计它们,可以轻松地将它们放入单独的对象中。 但是这一次,这些方法将已经在屏幕上产生了一些依赖性,并返回了一些结果。 积极的一点是,在这种情况下,签名是作为尽可能多的内容获得的,可以看出该功能需要其工作以及其结果是什么。 它可能看起来像这样:



分离的结构有助于保持方法标头的可读性和整洁度,因为可能存在许多依赖项。

重要的是要理解,该方法不必是整个对象中唯一的方法,它们是彼此独立的本质。 多亏了Rx,它们的输入和输出可以是异步的,并表示一个或多个可观察的,为数据操作提供了另一个维度。

这种方法可以使您的双手松开,并允许您实现几乎任何复杂的屏幕,同时保持其代码明确且松散耦合。

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


All Articles