在iOS中设计实体层次结构:类继承v组成

设计不佳的实体层次结构可能像这座塔一样不稳定。 不灵活的设计可能无法接受新的实体或特征,并且可能导致大量的代码复制和粘贴。 层次结构中更下层的实体可能会导致不必要的行为或意外的实现,并且大量的覆盖可能导致较差的动态调度。 简而言之,正确设计很重要!

设计这种层次结构有两种常用方法: 类继承 组合 ,您可能会惊讶地发现一种方法明显优于另一种方法。 在本文中,我们将使用这两种方法设计一个简单的层次结构,以讨论它们的优点和缺点。 让我们开始吧!

方法1:类继承

类继承是在OOP中设计实体层次结构的经典方法,即使是初学者,也将很快熟悉本节中的概念。 作为一点点回顾,让我们提醒自己,继承是一种基本行为,已植入到类中,但未在结构中找到。

出于本文的目的,我们将构建以下层次结构的代码表示形式:

在代码中,它看起来像这样:

在这种情况下,不能将任何功能放在Bird超类中,因为它们都不是所有四个Bird子类都通用的。 相反,我们有不同的组合。 为了使这些功能在需要的地方可用,我们被迫做这样的事情:

即使一眼就能看出这是一个糟糕的解决方案,原因有几个。 首先,有很多重复,将相同的方法实现复制到不同的类中。 试想一下,如果我们需要对这些方法之一进行更改,那么现在我们需要在多个位置进行相同的更改。

接下来,它也打破了单一责任原则。 就Puffin而言,我们在单个类中实现了所有三种方法。 为了代码的可维护性,我们理想地希望看到封装在不同实体中的这些不同职责。

有人可能会争辩说,可以通过一些“创造性”子类来减少复制的数量,但这不可避免地导致了继承杂耍行为,这种行为只会随着新方法的引入而变得更加复杂,或者新类仅需要某些继承的方法而变得更加复杂。 。 我们最终得到了一个非常脆弱的层次结构,在该层次结构中,超类的单个更改可能在继承链的更下方产生无法预测的结果。 除此之外,当我们查看开始的层次结构图时,我们被提醒,我们的理想是将所有鸟子类都置于同一级别,这是用这种方法无法实现的。

那么为什么类继承不能给我们想要的结果呢? 构架此讨论的一种有用方法是考虑“是”与“具有”的关系。 类继承仅限于表示“ is-a”关系:很好地描述了海雀 “ is-a” BirdBird “ is-a(n)” Animal 。 但是,当我们尝试表示“具有”关系时,通常会达不到要求。 在这种情况下,我们可以将“具有”表示为一种属性(“具有喙”)或一种方法(“具有飞行能力”)。 我们看到想要添加swim()walk()run()的功能未在我们的鸟子类中一致地应用,这并不罕见。 在您脑海中越是用“ is-a”和“ has-a”观察世界,您就越会意识到自然世界并不是那么容易装箱。

方法2:通过协议组成

因此,现在让我们通过合成解决相同的问题。 以下方法由四人帮提出,用一种优雅的方式描述了这种方法:

编程到接口,而不是实现。

在iOS中,我们通过使用协议定义接口。 任何采用协议的实体都必须在合同上遵守所要求的接口,这实质上就是上述短语要求我们执行的操作。 现在,我们的基本设置如下所示:

注意,我们有两个协议AnimalBird,后者继承了前者(第5行) 。 然后,我们有四个实体,每个实体都采用并遵守Bird协议。 现在,必须在我们的结构中实现“ 动物鸟类”中发现的任何特征。

在组合中,通常为个体行为创建协议,而不仅仅是为实体创建协议。 例如,在我们的Bird协议中,我们具有行为layEgg()。 当我们停止思考时,我们可以很快看到其他动物也产卵。 这将是其自己协议的主要候选者。 也许像:

注意,我们在Bird中使用的layEgg()方法现在已移到新协议EggLayable中 (第5-7行) 。 与仅允许一个类从单个超类继承的c lass继承不同,我们可以看到协议允许多重继承。 在我们的例子中, Bird可以继承AnimalEggLayable (第9行) 。 这是合成功能中最强大的功能之一,您可以选择想要的实体拥有(或不拥有!)的特征。 旁注:以各种组合方式重新打包协议可能是使用杂乱的协议可选项的好方法。

现在,让我们将walk()swim()fly()方法添加到此层次结构中。 因为我们希望这些行为可用于不是鸟类的其他动物,所以我们将像对待EggLayable一样为它们提供自己的协议。 但是,这一次,我只想实现这些方法一次,并让所有我的Bird实体共享一个实现。 为此,我们使用协议扩展。

这三个新协议在文件顶部声明,它们的方法作为默认实现放在扩展中。 现在,这四个结构中的每一个都采用其必需的WalkableSwimmableFlyable协议,您会注意到该结构本身不需要任何实现。

如果我们想对协议扩展中的默认实现做一个例外,我们可以简单地做到这一点:

现在, swim()方法出现在实体中(第12行) ,这意味着该实体永远不会访问Swimmable协议扩展中的默认实现。

包起来

放手,在设计稳定和灵活的层次结构时, 构图比 类继承要好。 我们的协议可以使用多重继承来获取他们想要的任何东西,而我们的实体可以类似地采用并遵循多个协议。 我们可以轻松地提供方法的默认实现,并在任何需要的地方创建特定于单个实体的行为。 现在,我们的示例实体层次结构不仅适用于我们可能要创建的任何未来鸟类实体,而且还适用于可能需要下蛋,游泳,飞行或行走的任何对象。

我想指出组成的最后一个优点。 当我们使用类继承时,我们永远不会问自己是否要让我们的实体类型通过引用或值来传递,因为继承仅在类中可用。 另一方面,组合为我们提供了有关是否使实体结构或类成为现实的选择。

和往常一样,感谢您的阅读!

后记:这个话题让我思考了多态性 -通过共享接口在层次结构中各个级别上使用不同类型的方式。 我写了一篇关于多态的文章,您可以在这里找到。