为什么要使用继承?

Kap*_*old 60 java language-agnostic oop inheritance composition

我知道之前已经讨论过这个问题,但似乎总是假设继承至少有时候比组合更好.我想挑战这个假设,希望获得一些理解.

我的问题是:既然你可以完成与对象组合东西,你可以用经典的继承和自经典的继承是很经常被滥用[1] ,自对象组合为您提供了灵活地改变委托对象运行时,为什么你会永远使用经典继承?

我可以理解为什么你会推荐Java和C++等语言中的继承,这些语言不能提供方便的委派语法.在这些语言中,只要不明显不正确,就可以使用继承来节省大量的输入.但是像Objective C和Ruby这样的其他语言提供了经典的继承非常方便的委托语法.Go编程语言是我所知道的唯一一种语言,它认为经典继承比它的价值更麻烦,并且仅支持代码重用的委托.

陈述我的问题的另一种方式是:即使您知道经典继承对于实现某个模型不是不正确的,那么这个理由是否足以使用它而不是组合?

[1]许多人使用经典继承来实现多态,而不是让他们的类实现接口.继承的目的是代码重用,而不是多态.此外,有些人使用继承来模拟他们对"is-a"关系的直观理解,这种关系往往是有问题的.

更新

我只想在谈到继承时澄清我的意思:

我在谈论一种继承的类型,即一个类继承自部分或完全实现的基类.我不是在谈论继承纯粹抽象的基类,这与实现接口相同,我记录的并不反对.

更新2

我知道继承是实现C++多态性的唯一方法.在这种情况下,显而易见的是你必须使用它.所以我的问题仅限于Java或Ruby等语言,这些语言提供了实现多态的不同方法(分别是接口和鸭子类型).

Jer*_*fin 49

继承的目的是代码重用,而不是多态.

这是你的根本错误.几乎完全恰恰相反.(公共)继承的主要目的是建模相关类之间的关系.多态性是其中很大一部分.

如果使用正确,继承不是重用现有代码.相反,它是现有代码使用.也就是说,如果您现有的代码可以使用现有的基类,那么当您从该现有基类派生一个新类时,其他代码现在也可以自动使用您的新派生类.

可以使用继承来重用代码,但是当/如果这样做,它通常应该是私有继承而不是公共继承.如果您使用的语言支持委派,那么您很少有充分的理由使用私有继承.OTOH,私有继承确实支持委托(通常)没有的一些事情.特别是,即使多态性是在这种情况下,一个决定性的次要问题,它可能仍然是一个问题-即与私有继承,你可以从一个基类,我们开始几乎你想要什么,并且(假设它允许)覆盖部分不太正确.

使用委托,您唯一真正的选择是完全按照现有的类使用它.如果它没有达到您想要的效果,那么您唯一真正的选择就是完全忽略该功能,并从头开始重新实现它.在某些情况下,这并没有损失,但在其他情况下则相当可观.如果基类的其他部分使用多态函数,则私有继承允许您覆盖多态函数,其他部分将使用重写函数.使用委派,您无法轻松插入新功能,因此现有基类的其他部分将使用您已覆盖的内容.

  • 经过一番思考之后,我还认为继承的目的是代码重用而不是多态.对于多态,它足以实现所需的接口,但是继承继承了实现. (2认同)
  • @ weberc2:如果你正在使用Go,那就是你唯一的选择.在实际上具有继承性的东西中,你所做的只是模仿继承已经做了什么,但是使它更难阅读,更难以使用,而且通常更糟糕.是的,许多人过度使用继承(特别是在Java中,从我所见过的),但是如果你想要所有(或至少90%)的继承提供,那么用手工模拟所有这些都不是*不是改进.哦,但是downvote让我的代表回到了5的倍数,所以即使它完全没有根据也谢谢. (2认同)
  • @ weberc2:完全不同意*什么*?到目前为止,与你所做的事实陈述最接近的事情是暗示你喜欢Go.我当然不同意这一点.你声称答案是客观错误的.如果是这样,引用一个句子(或两个,或其他),并指出它是如何错误的.提出关于模糊的问题我认为X或Y没有表现出客观上错误的答案. (2认同)

小智 16

使用继承的主要原因不是作为一种组合形式 - 它可以让你获得多态行为.如果你不需要多态,你可能不应该使用继承,至少在C++中.

  • @Kap嗯,我能说什么,除了"你错了" - C++(例如)没有接口的概念.我唯一一次在自己的代码中使用继承是我需要多态的时候. (4认同)
  • 我恭敬地不同意:继承不是*关于多态.这就是接口的用途.我同意多态性是最常用的继承,这是我的观点的一部分:继承被滥用于不适合的事物. (3认同)
  • @KaptajnKold:"他们获得了什么,如果他们使用了作曲,他们也不可能获得?" 老实说,我没有看到这个讨论的重点.国际海事组织你的问题太抽象,离现实太远而无用.为什么不简单地检查真正的API?如果你想知道从继承而不是其他技术中获得什么,只需找到基于继承的API,并使用你最喜欢的技术(你认为比继承更好的技术)重做它(至少部分).在这种情况下,您将获得所有利弊的现场演示. (3认同)
  • @Kap在C++中,仅从遗留类继承是最佳实践.我们大多数人在某个时刻躲过这个(后来后悔),但基类几乎总是抽象的 - 我不认为这是对我的限制. (2认同)
  • 那么,你几乎证明了我的观点:-) (2认同)
  • @Kap我认为你的观点是你可以用组合替换继承? (2认同)
  • 是的.但是您需要区分实现的继承和接口的继承(或纯粹的抽象类).我只是谈论前者. (2认同)
  • @Neil 不,我不同意。IMO 是您混淆了两个不同的概念,因为它们在 C++ 中共享相同的语法。Java 程序员不太容易混淆,因为这两个概念在语法上也不同。我承认我自己也有一点问题,因为我不知道有什么实质性的东西来描述我们都喜欢的继承类型,但在我看来,这根本不是真正的继承。如果您知道的话,请随时启发我。 (2认同)
  • Neil说:"在C++中,仅从遗留类继承是最佳实践." 他谈到了抽象类,但不一定是纯粹的*抽象类(对应于Java风格的接口).我认为继承一个非纯抽象的抽象类是完全没问题的. (2认同)
  • @Philipp你可能是对的,但我仍然无法理解*具体*的好处是什么:-( (2认同)
  • @Kap嗯,你似乎有自己独特的继承定义.而且我还不清楚你在问什么问题,从你的评论中,你也不是.但最后一次尝试 - 优秀的C++程序员不使用继承(经典或其他)作为组合的替代品. (2认同)
  • @Kap正如我所说(至少两次),C++中继承的目的是提供多态性; 要做到这一点,最好从抽象类派生.我不建议在其他情况下使用它.GoF书中的C++是非常糟糕的东西. (2认同)
  • @Neil,如果您觉得必须重复一遍,我很抱歉,但正是您声称仍然不理解我的问题,这就是我重述的原因。我不觉得我在理解你的观点时遇到了任何困难,事实上,我们之间的距离从未如此遥远:你明确不建议使用除抽象类之外的任何东西的继承。事实上,许多语言也支持*其他类型的继承*,你知道,你不推荐的那种,这让我提出了最初的问题。 (2认同)
  • @Kap,如果你指定'实现继承',你可能能够防止这种情况失控. (2认同)
  • @KaptajnKold:没有真实的例子,整个讨论看起来像某种关于不存在的飞行粉红色大象迁移的讨论.拿一个Qt或真正的GUI api.我没有看到你如何能够做到这一点(至少在C++中)没有继承.如果您认为可以采用不同的方式,请以不同的方式来说明您的观点.如果您认为可以在Go/Ruby/Whatever中完成,那么请使用该语言.比较结果(继承/没有继承),考虑差异,你会得到你的答案.纯粹抽象的讨论对我来说看起来有点无用. (2认同)

dsi*_*cha 12

如果您将未显式覆盖的所有内容委托给实现相同接口的其他对象("基础"对象),那么您基本上在组合的基础上进行Greenspunned继承,但(在大多数语言中)具有更多详细程度和样板.使用组合而不是继承的目的是,您只能委派要委派的行为.

如果您希望对象使用基类的所有行为,除非显式重写,那么继承是表达它的最简单,最简单,最直接的方式.

  • 组合的另一大优势是能够在运行时更改您委派的对象.它就像从父级继承,但是能够在运行时指定不同的父级继承.因此,即使您只是委托了所有方法,仍然有很大的优势.特别是在测试和嘲笑时. (6认同)
  • 是的,继承只是一个奇怪的组合特殊情况,无法合理地修复内在元素.可能存在一种情况,继承可能会为您节省一些键击,但它并不能证明它创建的所有混淆和不适当的用法. (3认同)
  • 如果上面的“Greenspunned”这个词不熟悉,它意味着将另一种语言的功能带入您自己的语言中,但实现得很糟糕。请参阅[Greenspun 第十条规则](https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule) 和[Leaky Greenspunned Abstractions](https://raganwald.com/2013/08/01/leaky-greenspunned-abstractions)。 html)。 (2认同)

fai*_*gat 7

每个人都知道多态性是继承的一大优势.我在继承中发现的另一个好处是有助于创建现实世界的复制品.例如,在付费滚动系统中,如果我们使用超类Employee继承所有这些类,我们会处理经理开发人员,办公室男生等.它使我们的程序在现实世界的背景下更容易理解,所有这些类基本上都是员工.还有一个类不仅包含它们还包含属性的方法.因此,如果我们在Employee类中包含通用于员工的属性,如社会安全号码年龄等,它将提供更多的代码重用和概念清晰度,当然还有多态性.然而,在使用继承的东西时,我们应该牢记基本的设计原则"


Aar*_*lla 6

如果符合以下情况,则首选继承:

  1. 您需要公开您扩展的类的整个API(使用委托,您将需要编写许多委托方法),并且您的语言没有提供一种简单的方式来表示"委派所有未知方法".
  2. 对于没有"朋友"概念的语言,您需要访问受保护的字段/方法
  3. 如果您的语言允许多重继承,则委派的优点会有所减少
  4. 如果您的语言允许在运行时动态继承类或甚至实例,则通常根本不需要委派.如果您可以同时控制暴露的方法(以及它们如何暴露),则根本不需要它.

我的结论:委托是一种编程语言中的错误的解决方法.


Cha*_*ion 5

在使用继承之前我总是三思而后行,因为它很快就会变得棘手。话虽如此,在很多情况下它只是生成最优雅的代码。

  • “在很多情况下,它只是生成最优雅的代码。” 给我看一个。 (10认同)