我必须承认我有点像OOP怀疑论者。面向对象的不良教学和实验经验无济于事。所以我转变成了Visual Basic中的狂热信徒(经典之作!)。
然后有一天我发现C ++发生了变化,现在有了STL和模板。我真的很喜欢!使语言有用。然后另一天MS决定对VB进行面部手术,我真的很讨厌无端变化的最终结果(使用“end while”代替“wend”会让我成为一个更好的开发者?为什么不放下“next”for“结束了“,也是为什么?为什么强迫吸气者与安装者一起?等等?加上如此多的Java功能,我觉得这些功能毫无用处(例如继承,以及分层框架的概念)。
现在,几年后,我发现自己在问这个哲学问题:遗产真的需要吗?
四人一组说我们应该赞成对象组合而不是继承。在考虑它之后,我找不到你可以用继承做的事情,你不能用对象聚合和接口做。所以我想知道,为什么我们首先要拥有它?
有什么想法吗?我很想看到一个例子,说明哪些地方肯定需要继承,或者使用继承代替composition +接口可以导致更简单和更容易修改的设计。在以前的工作中,我发现如果你需要更改基类,你还需要修改几乎所有的派生类,因为它们取决于父行为。如果你让基类'方法虚拟......那么就不会发生很多代码共享:(
另外,当我最终创建自己的编程语言(我发现大多数开发人员共享的长期未满足的愿望)时,我认为添加继承没有任何意义......
答案 0 :(得分:33)
真的很简短的答案:不需要继承,因为只需要字节代码。但显然,字节代码或汇编不是编写程序的实际方法。 OOP不是编程的唯一范例。但是,我离题了。
我在21世纪初进入计算机科学专业,当时继承(是a),作文(有a)和接口(做a)是在平等的基础上教授的。因此,我使用非常少的继承,因为它通常更适合于组合。这是强调的,因为许多教授因为滥用继承而看到了错误的代码(以及你所描述的内容)。
无论是否使用遗传创建语言,您是否可以创建一种编程语言来防止不良习惯和糟糕的设计决策?
答案 1 :(得分:22)
我认为要求继承真正所需的情况稍微缺少一点。您可以通过使用接口和一些组合来伪造继承。这并不意味着继承是无用的。你可以在汇编代码中用VB6做一些额外的打字,这并不意味着VB6没用。
我通常只是开始使用界面。有时我注意到我实际上想要继承行为。这通常意味着我需要一个基类。就这么简单。
答案 2 :(得分:14)
继承定义了“Is-A”关系。
class Point( object ):
# some set of features: attributes, methods, etc.
class PointWithMass( Point ):
# An additional feature: mass.
上面,我使用继承来正式声明PointWithMass是一个Point。
有几种方法可以处理作为PointWithMass和Point的对象P1
。这是两个。
从PointWithMass
对象p1
引用某个Point对象p1-friend
。 p1-friend
具有Point
个属性。当p1
需要参与Point
类行为时,需要将工作委托给其朋友。
依靠语言继承来确保Point
的所有功能也适用于我的PointWithMass
对象p1
。当p1
需要参与Point
之类的行为时,它已经 一个Point
对象,并且可以执行需要完成的操作。
我宁愿不管理浮动的额外对象,以确保所有超类特征都是子类对象的一部分。我宁愿继承,以确保每个子类都是它自己的类的实例,并且也是所有超类的实例。
编辑。
对于静态类型的语言,有一个奖励。当我依靠语言来处理此问题时,PointWithMass
可以在预期Point
的任何地方使用。
对于非常模糊的滥用继承,请阅读C ++奇怪的“通过私有继承构成”的泥潭。有关此问题的进一步讨论,请参阅Any sensible examples of creating inheritance without creating subtyping relations?。它混合了遗产和构成;它似乎没有为结果代码增加清晰度或精度;它只适用于C ++。
答案 3 :(得分:12)
GoF(以及其他许多人)建议您仅支持组合而不是继承。如果你有一个具有非常大的API的类,并且你只想添加非常少量的方法,只留下基本实现,我会发现使用组合是不合适的。您必须重新实现封装类的所有公共方法才能返回它们的值。当你可以继承所有这些行为时,浪费时间(程序员和CPU),花时间专注于新方法。
所以,要回答你的问题,不,你绝对没有需要继承。然而,在许多情况下,这是正确的设计选择。
答案 4 :(得分:11)
继承的问题在于它混淆了子类型(断言is-a关系)和代码重用的问题(例如,私有继承仅用于重用)。
所以,不,这是我们不需要的重载词。我更喜欢子类型(使用'implements'关键字)和import(有点像Ruby在类定义中做的那样)
答案 5 :(得分:7)
继承允许我将一大堆簿记推送到编译器上,因为它为我提供了对象层次结构的多态行为,否则我将不得不创建和维护自己。无论一个银弹OOP有多好,总会有一些你希望采用某种行为的情况,因为这样做才有意义。最终,这就是OOP的重点:它使某类问题更容易解决。
答案 6 :(得分:3)
继承是一项实施决策。接口几乎总是代表一种更好的设计,通常应该在外部API中使用。
为什么当编译器为你继承时,为编写的成员对象写了很多样板代码转发方法调用?
This answer另一个问题总结了我的想法。
答案 7 :(得分:3)
组合的缺点是可能伪装元素的相关性,而可能可能让其他人更难理解。比如说,一个2D Point类以及将它扩展到更高维度的愿望,你可能必须添加(至少)Z getter / setter,修改getDistance(),并且可能添加一个getVolume()方法。所以你有对象101元素:相关的状态和行为。
具有成分思维模式的开发人员可能已经定义了getDistance(x,y) - >双方法,现在定义一个getDistance(x,y,z) - >双重方法。或者,一般来说,他们可能会定义一个getDistance(lambdaGeneratingACoordinateForEveryAxis()) - >双重方法。然后他们可能会编写createTwoDimensionalPoint()和createThreeDimensionalPoint()工厂方法(或者可能是createNDimensionalPoint(n))来拼接各种状态和行为。
具有OO思维模式的开发人员将使用继承。在域特性的实现中具有相同的复杂性,在初始化对象方面较少复杂(构造函数负责处理它而不是Factory方法),但在可以初始化方面不够灵活
现在从可理解性/可读性的角度考虑它。要理解组合,可以在另一个函数内以编程方式组成大量函数。因此,静态代码“结构”(文件和关键字等)几乎没有使Z和distance()的相关性跳出。在OO世界中,你有一个巨大的闪烁红灯告诉你层次结构。此外,您有一个基本上通用的词汇来讨论结构,广为人知的图形符号,自然层次结构(至少对于单继承)等。
现在,另一方面,一个名称和构造良好的Factory方法通常会更多地显示状态和行为之间有时模糊的关系,因为组合思维集有助于功能代码(即通过状态传递状态的代码)参数,而不是这个)。
在经验丰富的开发人员的专业环境中,构图的灵活性通常胜过其更抽象的本质。但是,人们永远不应该忽视可理解性的重要性,特别是在具有不同程度经验和/或高流动率的团队中。
答案 8 :(得分:3)
还有其他人还记得所有OO纯粹主义者对“遏制”的COM实施而不是“继承”进行弹道导弹吗?它实现了基本相同的东西,但具有不同类型的实现。这让我想起了你的问题。
我严格地试图避免软件开发中的宗教战争。 (“vi”或“emacs”...当每个人都知道它的“vi”!)我认为它们是小思想的标志。 Comp Sci教授可以坐下来讨论这些事情。我在现实世界工作,可以少关心。所有这些都只是尝试为实际问题提供有用的解决方案。如果它们有效,人们就会使用它们。事实上,OO语言和工具已经在20年内大规模商业化,这是一个很好的选择,它们对许多人都很有用。
答案 9 :(得分:3)
编程语言中有很多功能不是真的需要。但它们存在的原因有很多,基本上归结为可重用性和可维护性。
所有关注的事业都是(产品质量)便宜和快速。
作为开发人员,您可以通过提高效率和工作效率来实现这一目标。所以你需要确保你编写的代码很容易可重用和可维护。
除此之外,这就是继承为您提供的功能 - 能够重用而无需重新发明轮子,以及轻松维护您的基础对象的能力无需对所有类似物体进行维护。
答案 10 :(得分:2)
继承有很多有用的用法,可能也有许多不太有用的用法。其中一个有用的是流类。
您有一个应该能够传输数据的方法。通过使用流基类作为方法的输入,可以确保您的方法可以用于写入多种流而无需更改。通过网络,压缩等到文件系统
答案 11 :(得分:2)
取决于您对“需要”的定义。不,没有任何东西不可能没有继承,尽管替代方案可能需要更详细的代码,或者重写您的应用程序。
但是肯定存在继承有用的情况。正如你所说,组合加接口几乎涵盖了所有情况,但如果我想提供默认行为呢?界面不能这样做。基类可以。有时候,你想要做的只是覆盖个别方法。不是从头开始重新实现类(与接口一样),而只是改变它的一个方面。或者您可能不希望班级的所有成员都被覆盖。也许您只希望用户覆盖一个或两个成员方法,其余的调用这些(并在用户重写方法之前和之后执行验证和其他重要任务)被指定一次对于基类中的所有人而言,并且不能被覆盖。
对于那些过于沉迷于Java对OOP的狭隘定义(和对OOP的迷恋)的人来说,继承通常被用作拐杖,而且在大多数情况下我同意,这是错误的解决方案,就好像你的类层次结构越深,更好的软件。
答案 12 :(得分:2)
没有
对我来说,OOP主要是关于状态和行为以及多态的封装。就是这样。但是如果你想要静态类型检查,你需要一些方法来分组不同的类型,所以编译器可以检查,同时仍允许你使用新类型代替另一种相关类型。创建类型层次结构允许您对类型和类型组使用相同的概念(类),因此它是使用最广泛的形式。
但还有其他方法,我认为最常见的是鸭子打字,以及密切相关的基于原型的OOP(实际上不是继承,但它通常称为基于原型的继承)。
答案 13 :(得分:1)
我同意其他所有人关于必要/有用的区别。
我喜欢OOP的原因是因为它让我编写的代码更清晰,逻辑更有条理。其中一个最大的好处来自能够“分解”逻辑,这是许多类共有的。我可以给你一些具体的例子,其中OOP严重降低了我的代码的复杂性,但这对你来说很无聊。
我只想说,我心地OOP。
答案 14 :(得分:1)
绝对需要?没有, 但想想灯具。您可以在每次制作新灯时从头开始创建一个新灯,或者您可以从原始灯中获取属性,并制作各种与原始灯具有相同属性的新灯具,每种灯具都有自己的风格。
或者你可以从头开始制作一盏新灯,或者告诉别人以某种方式看它以便看到灯光,或者,或者
不是必需的,但很好:)
答案 15 :(得分:1)
不需要,但很有用。
每种语言都有自己的方法来编写更少的代码。 OOP有时会变得复杂,但我认为这是开发人员的责任,OOP平台在使用得很好时非常有用。
答案 16 :(得分:1)
感谢大家的回答。我坚持认为,严格来说,不需要继承,但我相信我对此功能有了新的认识。
其他的东西:在我的工作经历中,我发现继承导致更简单,更清晰的设计,当它被引入项目的后期时,注意到很多类具有很多共性并且你创建了一个基类。在从一开始就创建了一个宏模式的项目中,在继承层次结构中有很多类,重构通常是痛苦和困难的。
看到一些提及类似内容的答案让我想知道这可能不是应该如何使用继承:事后。让我想起斯捷潘诺夫的一句话:“你不是从公理开始,在你得到一堆相关的证据之后最终会得到公理”。他是一名数学家,所以他应该知道一些事情。
答案 17 :(得分:1)
当子类与超类实际上是同一种对象时,继承是一件好事。例如。如果您正在实现Active Record模式,那么您正在尝试将类映射到数据库中的表,并将类的实例映射到数据库中的行。因此,您的Active Record类很可能共享一个公共接口和方法的实现,例如:什么是主键,当前实例是否持久化,保存当前实例,验证当前实例,在验证时执行回调以及/或保存,删除当前实例,运行SQL查询,返回类映射到的表的名称等。
从你如何表达自己认为继承是单一但不是多重的问题来看,似乎也是如此。如果我们需要多重继承,那么我们必须使用接口和组合来完成工作。为了说明一点,Java假设实现继承是单一的,接口继承可以是多个。人们不需要走这条路。例如。 C ++和Ruby允许为您的实现和接口进行多重继承。也就是说,应谨慎使用多重继承(即保持抽象类虚拟和/或无状态)。
也就是说,正如您所说,现实生活中的类层次结构太多,其中子类从超级类继承而不是承载真正的is-a关系。因此,超类的变化会对子类产生副作用,这并不足为奇。
答案 18 :(得分:1)
接口的最大问题是它们无法更改。创建一个公共接口,然后更改它(向它添加一个新方法)并打破全世界的百万个应用程序,因为它们实现了您的接口,但不是新方法。应用程序甚至可能无法启动,VM可能拒绝加载它。
使用其他程序员可以继承的基类(非抽象)(并根据需要覆盖方法);然后添加一个方法。使用你的类的每个应用程序仍然可以工作,这个方法不会被任何人覆盖,但是既然你提供了一个基本实现,那么这个将被使用,并且它可以适用于你的类的所有子类...它可能也会引起奇怪的行为,因为有时候覆盖它本来是必要的,好吧,可能就是这样,但至少世界上所有这百万个应用程序仍然会启动!
我宁愿让我的Java应用程序在将JDK从1.6更新到1.7之后仍然运行一些小错误(可以随着时间的推移而修复)而不是让它完全运行它(强制立即修复或者它将毫无用处)人)。
答案 19 :(得分:1)
//我发现这个QA非常有用。许多人已经回答了这个问题但我想补充一下......
1:能够为插件开发人员定义抽象接口 - 例如,。当然,你可以使用函数指针,但这更好更简单。
2:继承有助于非常接近实际关系的模型类型。有时会在编译时捕获大量错误,因为您具有正确的类型层次结构。例如,shape <-- triangle
(假设有很多代码需要重用)。您可能希望使用shape
对象组合三角形,但shape
是不完整的类型。插入像double getArea() {return -1;}
这样的虚拟实现可以,但是你正在为错误留出空间。有一天return -1
会被执行!
3:void func(B* b); ... func(new D());
隐式类型转换为Derived
Base
提供了极好的符号方便性。我记得曾读过斯特劳斯特鲁普说,他希望像基本数据类型(因此重载运算符等)一样创建一流的公民。从Derived到Base的隐式转换,就像从数据类型到更宽泛的兼容(从short到int)的隐式转换。
答案 20 :(得分:1)
继承和撰写各有利弊。
请参阅此相关的SE问题,了解继承的优点和构成的缺点。
Prefer composition over inheritance?
查看此documentation link中的示例:
该示例通过使用继承作为实现多态的手段来显示覆盖的不同用例。
答案 21 :(得分:0)
在下文中,继承用于为同一类型的所有几个特定化身呈现特定属性。在这种情况下,GeneralPresenation具有与所有“表示”(传递给MVC视图的数据)相关的属性。 Master Page是唯一使用它并期望GeneralPresentation的东西,尽管具体的视图期望更多的信息,根据他们的需求量身定制。
public abstract class GeneralPresentation
{
public GeneralPresentation()
{
MenuPages = new List<Page>();
}
public IEnumerable<Page> MenuPages { get; set; }
public string Title { get; set; }
}
public class IndexPresentation : GeneralPresentation
{
public IndexPresentation() { IndexPage = new Page(); }
public Page IndexPage { get; set; }
}
public class InsertPresentation : GeneralPresentation
{
public InsertPresentation() {
InsertPage = new Page();
ValidationInfo = new PageValidationInfo();
}
public PageValidationInfo ValidationInfo { get; set; }
public Page InsertPage { get; set; }
}