使用协议掌握泛型:规范模式

如果您正在阅读本文,那么您很可能已经知道使用泛型的好处。 您想要减少编码。 您希望编写的代码能做更多。 您以前曾尝试在编程中使用泛型,但有可能使您从未想到的挫败感达到顶点。 你不是一个人。 能够以一种通用的方式工作,但是仍然可以成功地推断出您的类型可能很棘手。 幸运的是,将泛型与协议的使用结合起来确实很有效,而且一旦您学习了少量技术,您就会发现这棘手的事情实际上并不是那么棘手。

为了探讨该主题,我们将构建和完善一个针对以下四个方面的规范设计模式: (我希望)这些技术将成为您的编码库中的主要内容。 顺便说一句,这种模式是解决开放-封闭原则的极好方法。 我强烈建议您熟悉的东西,以及我最近 在这里 写的一些东西

过滤产品

我们的假设情况是,我们有一个称为Product的实体,并且我们希望能够按某种规格过滤一系列产品。 首先让我们看一下Product

这是有道理的,因为我们的协议类型Specification是泛型的,而且我们可以可行地添加一个规范实例,该实例的关联类型不是Product 。 我们需要做的是检查items参数中的类型是否与spec参数中的类型相同。 让我们尝试一下。

技术2:检查通用实例上的条件

我们可以使用以下模式在函数声明的开头执行检查:

func someFunc (参数:T)其中T.someProperty == self.someProperty

请注意,我们在输入参数之前使用了典型的占位符语法 ,以表明我们的函数正在使用泛型类型。 为了能够访问T实例的内部,我们需要首先说一下实例的类型是 ; 在这种情况下, T的类型为SomeType 。 接下来,我们需要表明我们要检查使用where关键字完成的条件。 最后,我们可以访问T.someProperty类型的属性并执行布尔检查。 现在,只要通用类型为SomeType且其值someProperty与我们自己的类型相匹配,该函数就可以使用。

考虑到这一点,让我们构建一个通用过滤器 ,该过滤器可以在规范associatedtype与物料类型匹配的情况下接受任何物料和任何规格。

现在,代替将specs参数类型设置为Specification ,我们可以改用Spec,因为它具有与我们Filter (第5行) 中的那个相关联的关联类型 ,这是安全的。 创建协议后,实现Filter本身非常简单。 我们遵循协议(第9行),设置类型别名 (第10行),然后filter(items:,specs 🙂神奇地将其自身设置为正确的类型。 现在,我们可以使用我们先前制作的Product实例进行测试

在这一点上,我们可以肯定地说一切正常。 使用我们当前的设计,创建更多的过滤器和规格是一项非常简单的任务。 但是,让我们看看是否可以进一步完善它。 目前,我们的ColorSpecification实体仅适用于Product类型的实例。 ColorSpecification能够与任何具有颜色的类型配合使用是否合理 ? 想一想,我们不能对我们的SizeSpecification和我们的ProductFilter说同样的话吗? 现在是时候让我们的实体像我们的协议一样通用。

技术3:使用静态初始化设置关联类型

为了使我们的实体通用,我们需要使用通用占位符语法,就像我们在上一节中所做的一样。 看起来是这样的:

struct MyStruct :ProtocolType {}

在这种情况下,当我们定义实体时,我们将遵循我们的ProtocolType,T分配给relatedtype。 现在我们可以使用任何类型的工具! 初始化时,需要使用以下语法:

让a = MyStruct ()

现在,将这种技术应用于我们现有的设计中:

第一步是为大小彩色 (第2-8行)创建协议,并让我们的产品采用每种协议。 您将记住,我们的产品已经具有协议中定义的属性(颜色和大小),因此我们无需在其中添加任何内容。 现在我们的两个规范实体使用来分配关联的类型 (第 15、24 行) 。 因为Product符合这些协议,所以我们可以将其设置为我们的类型,但是只要它也具有必要的协议采用性,我们就可以轻松地设置任何其他类型。 您会注意到,我们在SizeSpecification的实例化中使用了 (第49行) ; 但是,在创建GenericFilter时不需要这样做,因为使用通用T的唯一位置在filter(items:,specs:)中,并且可以通过上一节中创建的检查来推断类型(第36-37行) )

现在我们可以真正地说我们拥有一个通用的,可重用的设计。 但是,还有一个承诺尚未兑现。 在开始时,我们决定我们希望能够按一种规格而不是多种规格进行过滤。 我们希望能够实现这一目标而无需在过滤器中编写任何新方法,而这将我们带到本文的最终技术。

技术4:使用协议类型进行递归设计

您会注意到,我们的filter方法的输入参数类型为Spec (第36行) ,因此我们只能添加一个符合Specification协议的实例。 但是,只要满足要求,我们就可以实际检查任意多的属性。 目前,我们的规范类型是标量(单个单位),但是没有理由为什么我们不能在另一个规范类型中封装多个规范类型。 这称为递归对象。 这是我们可以通过结合使用先前探索的技术来实现此目的的方法:

我们的AndSpecification (第2行)的定义中有很多事情要做,所以 让我们分解一下。 首先,请注意我们的实体符合规范本身,因此可以将其传递到我们的过滤器中。 我们还具有SpecA和SpecB类型的两个属性,每个属性都符合规范协议。 这意味着我们总共要满足三种关联类型。 为此,我们声明了三个通用类型TSpecASpecB 。 为了确保一切正常,我们需要检查所有三个泛型的关联类型是否相同。 我们在where语句中执行此操作。 我们还需要创建一个自定义的初始化程序(第7行),因为所有通用检查都足以使编译器无法创建我们期望用于结构的成员式初始化程序。

在用例中,您会看到红色较小的 Specification用类型Product进行了静态初始化(第18-19行) 。 对于AndSpecification的初始化,这不是必需的,因为对初始化程序的输入参数执行的类型检查足以推断类型T。

另请注意, AndSpecification的递归性质意味着您可以将AndSpecification的另一个实例作为初始化程序中的输入参数之一进行传递,实际上允许您按任意数量的规范进行过滤。

包起来

在本文中,我们创建并完善了规范设计模式,以实现所需的抽象水平。 现在,我们可以按任意数量的规范过滤任何对象,并且这些规范本身可以在任意数量的实体中重复使用。 我们通过以面向协议的方式使用泛型来实现这一目标。 我们讨论了四种基本技术,当它们结合使用时,它们足够强大,可以帮助您实现所需的几乎任何抽象。

您可以在此处克隆此项目的git存储库

感谢您的阅读!