为什么更偏重于继承而不是继承?每种方法都有哪些取舍?什么时候应该选择继承而不是合成?
为什么更偏重于继承而不是继承?每种方法都有哪些取舍?什么时候应该选择继承而不是合成?
Answers:
优先考虑组成而不是继承,因为它在以后更易于延展/更容易修改,但不要使用总是组合的方法。通过组合,可以轻松地通过“依赖注入/设置器”即时更改行为。继承更加严格,因为大多数语言不允许您从一种以上的类型派生。因此,一旦从TypeA派生,鹅或多或少就会被煮熟。
我对上面的酸测试是:
TypeB是否要公开TypeA的完整接口(所有公共方法不少于此),以便可以在需要TypeA的地方使用TypeB?表示继承。
TypeB是否只希望TypeA公开某些/部分行为?表示需要组成。
更新:刚刚回到我的答案,现在似乎没有完整提及Barbara Liskov的Liskov替代原则,这是不完整的,以检验“我是否应该继承这种类型?”
将遏制视为有关系。汽车“具有”发动机,人“具有”名称,等等。
想继承的作为是一个关系。汽车“是”车辆,人“是”哺乳动物,等等。
如果您了解其中的区别,则更容易解释。
一个这样的例子是不使用类的PHP(特别是在PHP5之前)。所有逻辑都编码为一组功能。您可以包括其他包含辅助功能的文件,等等,并通过在功能中传递数据来进行业务逻辑。随着应用程序的增长,这可能很难管理。PHP5试图通过提供更多面向对象的设计来对此进行补救。
这鼓励使用类。继承是OO设计的三个宗旨之一(继承,多态,封装)。
class Person {
String Title;
String Name;
Int Age
}
class Employee : Person {
Int Salary;
String Title;
}
这是工作中的继承。雇员“是”个人或从个人继承。所有继承关系都是“是”关系。Employee还会遮盖Person的Title属性,这意味着Employee.Title将返回Employee而不是Person的Title。
合成胜于继承。简单地说,您将拥有:
class Person {
String Title;
String Name;
Int Age;
public Person(String title, String name, String age) {
this.Title = title;
this.Name = name;
this.Age = age;
}
}
class Employee {
Int Salary;
private Person person;
public Employee(Person p, Int salary) {
this.person = p;
this.Salary = salary;
}
}
Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);
组成通常是“具有”或“使用”关系。这里的Employee类有一个Person。它不是从Person继承而来的,而是将Person对象传递给它的,这就是为什么它“具有” Person的原因。
现在说您要创建一个Manager类型,因此最终得到:
class Manager : Person, Employee {
...
}
此示例可以正常工作,但是,如果Person和Employee都声明了Title
怎么办?Manager.Title应该返回“运营经理”还是“先生”?在组成下,这种歧义可以更好地解决:
Class Manager {
public string Title;
public Manager(Person p, Employee e)
{
this.Title = e.Title;
}
}
Manager对象由Employee和Person组成。标题行为来自员工。这种明确的构成消除了其他方面的歧义,并且您将遇到较少的错误。
继承带来了所有不可否认的好处,这是它的一些缺点。
继承的缺点:
另一方面,对象组成是在运行时通过对象获取对其他对象的引用来定义的。在这种情况下,这些对象将永远无法访问彼此的受保护数据(没有封装中断),并且将被迫尊重彼此的接口。并且在这种情况下,实现依赖也将比继承情况少得多。
简而言之,我会同意“优先考虑组成而不是继承”,但对我而言,这听起来常常像是“优先考虑土豆而不是可口可乐”。有继承的地方和组成的地方。您需要了解差异,然后这个问题就会消失。对我来说真正的意思是“如果您要使用继承-再想一想,您需要组合”。
当您想吃东西时,您应该选择土豆而不是可口可乐,而当您想要喝时,您应该更喜欢可口可乐。
创建子类不仅仅意味着调用超类方法的简便方法。当子类“ is-a”超类在结构上和功能上都可以使用时,您应该使用继承。如果不是这种情况-它不是继承,而是其他东西。合成是指您的对象由另一个组成或与它们有某种关系。
因此对我来说,好像某人不知道是否需要继承或构成,真正的问题是他不知道自己是否想喝酒或吃东西。多考虑您的问题领域,更好地理解它。
InternalCombustionEngine
具有派生类的基类GasolineEngine
。后者添加了诸如火花塞之类的东西,这是基类所缺少的,但是将其用作InternalCombustionEngine
会导致火花塞被使用。
继承是非常诱人的,尤其是从程序领域来的继承,它看起来通常看起来很优雅。我的意思是,我需要做的就是将这一功能添加到其他类中,对吗?好吧,问题之一是
基类通过将实现细节以受保护成员的形式公开给子类来破坏封装。这会使您的系统僵化而脆弱。然而,更悲惨的缺陷是新的子类带来了继承链的所有包and和观点。
文章Inheritance is Evil:DataAnnotationsModelBinder的史诗失败,逐步介绍了C#中的一个示例。它显示了应该使用组合物时继承的使用方式以及如何将其重构。
在Java或C#中,对象一旦实例化就无法更改其类型。
因此,如果您的对象需要显示为不同的对象或根据对象的状态而不同地表现,请使用“ 组成”:请参阅状态和策略设计模式。
如果对象必须是同一类型,则使用继承或实现接口。
Client
。然后,PreferredClient
弹出一个新的概念。应该PreferredClient
继承Client
吗?首选客户毕竟是“客户”,不是吗?好吧,不是那么快...就像您说的那样,对象无法在运行时更改其类。您将如何模拟client.makePreferred()
操作?也许答案在于使用缺少概念的合成,Account
也许吗?
Client
课程,也许只是一个封装的概念Account
可能是一个StandardAccount
或一个PreferredAccount
...
在这里找不到满意的答案,所以我写了一个新的答案。
了解为什么“ 更喜欢继承而不愿继承”,我们首先需要找回在这个简短成语中省略的假设。
继承有两个好处:子类型化和子类化
子类型化意味着符合类型(接口)签名,即一组API,并且一个子类型可以覆盖部分签名以实现子类型多态性。
子类化意味着方法实现的隐式重用。
这两个好处带来了进行继承的两个不同目的:面向子类型和面向代码重用。
如果仅代码重用是唯一目的,则子类提供的功能可能比他所需要的多,即子类的某些公共方法对子类没有多大意义。在这种情况下,代替偏袒继承组合物,组合物被要求。这也是“ is-a”与“ has-a”概念的来源。
因此,只有在打算使用子类型化时,即以后以多态的方式使用新类时,我们才面临选择继承或组合的问题。这是在所讨论的简短习语中被忽略的假设。
子类型要符合类型签名,这意味着组合程序必须始终公开不少于该类型的API。现在开始权衡取舍:
继承提供了直接的代码重用(如果不被覆盖的话),而编写必须重新编码每个API,即使这只是委托的简单工作。
继承提供简单的开递归经由内部多态性位点this
,即,调用重写方法(或者甚至类型中另一个成员函数),可以是公共或私有的(虽然泄气)。可以通过composition来模拟开放递归,但是这需要额外的精力,并且可能并不总是可行的(?)。重复问题的答案与此类似。
继承公开受保护的成员。这破坏了父类的封装,并且如果由子类使用,则会在子代及其父代之间引入另一个依赖关系。
组合具有易于多重继承的优点。
考虑到上述折衷因素,因此我们更倾向于使用组合而不是继承。但是对于紧密相关的类,即当隐式代码重用确实能带来好处,或者需要开放递归的神奇力量时,继承将是选择。
我个人学会了始终偏向于继承而不是继承。没有可以通过继承解决的程序化问题,而不能通过组合解决。尽管在某些情况下可能必须使用Interfaces(Java)或Protocols(Obj-C)。由于C ++一无所知,因此您必须使用抽象基类,这意味着您无法完全摆脱C ++中的继承。
组合通常更合乎逻辑,它提供更好的抽象,更好的封装,更好的代码重用(尤其是在非常大的项目中),并且仅因为您在代码中的任何地方进行了孤立的更改,就不太可能在远处破坏任何内容。这也使坚持“ 单一责任原则 ” 变得更加容易,该原则通常被总结为“ 班级变更的理由永远不只一个。 ”,这意味着每个类别的存在都是出于特定的目的,因此应该仅具有与其用途直接相关的方法。另外,由于继承树很浅,即使您的项目开始变得很大,也更容易保留概述。许多人认为继承代表了我们现实世界很好,但这不是事实。现实世界中使用的构成多于继承。几乎可以握在手中的每个现实对象都是由其他较小的现实对象组成的。
但是,在组合方面也有缺点。如果您完全跳过继承而只关注合成,您会注意到,您经常不得不编写一些额外的代码行,如果您使用了继承,则这些代码行是不必要的。您有时还被迫重复自己,这违反了 DRY原则(干=不要重复自己)。同样,组合通常需要委托,而一个方法只是调用另一个对象的另一个方法,而此调用周围没有其他代码。这样的“双重方法调用”(可能会很容易地扩展为三重或四重方法调用,甚至更远),其性能比继承要差得多,在继承中,您仅继承父方法。调用继承的方法可能与调用非继承的方法一样快,或者可能稍慢一些,但通常仍比两个连续的方法调用快。
您可能已经注意到,大多数OO语言不允许多重继承。虽然在很多情况下多重继承可以真正为您带来收益,但是这些都是例外,而不是规则。每当您遇到“多重继承将是解决此问题的一个非常酷的功能”的情况时,通常您都应该重新考虑继承,因为即使这样,它也可能需要几个额外的代码行,基于组合的解决方案通常会变得更优雅,更灵活且更适合未来。
继承确实是一个很酷的功能,但是恐怕最近几年它已经被滥用了。人们将继承视为可以钉牢这一切的锤子,无论它实际上是钉子,螺钉还是完全不同的东西。
TextFile
是一个File
。
我的一般经验法则:在使用继承之前,请考虑组合是否更有意义。
原因:子类化通常意味着更多的复杂性和联系性,即更容易更改,维护和扩展而不犯错误。
Sun的蒂姆·布德罗(Tim Boudreau)给出了更加完整和具体的答案:
在我看来,使用继承的常见问题是:
- 无辜的行为会产生意想不到的结果 -经典示例是在初始化子类实例字段之前,从超类构造函数调用可重写方法。在理想世界中,没有人会这样做。这不是一个完美的世界。
- 它为子类提供了不正确的诱惑,使他们无法对方法调用的顺序进行假设,并且这种假设-如果超类可能随时间演变,则这种假设往往不稳定。另请参阅我的烤面包机和咖啡壶类比。
- 类变得更重 -您不一定知道您的超类在其构造函数中正在执行什么工作,或者将使用多少内存。因此,构造一些无辜的轻量级对象可能比您想象的要昂贵得多,而且如果超类不断发展,这种情况可能会随着时间而改变。
- 它鼓励子类的爆炸式增长。类加载会花费时间,更多类会消耗内存。在处理NetBeans规模的应用程序之前,这可能不是问题,但是在那儿,我们遇到了一些实际问题,例如菜单速度慢,因为菜单的首次显示会触发大量的类加载。我们通过使用更具声明性的语法和其他技术来解决此问题,但这也需要花费时间来解决。
- 这使得以后更改内容变得更加困难 -如果您已公开一个类,则交换超类将破坏子类-这是一个选择,一旦您将代码公开,便成为您的选择。因此,如果您不更改超类的实际功能,则可以在以后使用时有更大的自由来进行更改,而不必扩展所需的内容。以子类化JPanel为例-这通常是错误的。如果子类在某个地方是公共的,则您将永远没有机会重新审视该决定。如果以JComponent getThePanel()的形式访问它,则仍然可以执行此操作(提示:以您的API公开内部组件的模型)。
- 对象层次结构无法扩展(或者使它们后来扩展比预先计划要困难得多) -这是经典的“层数过多”问题。我将在下面进行介绍,以及AskTheOracle模式如何解决它(尽管它可能会冒犯OOP纯粹主义者)。
...
如果允许继承,那么我的看法是:
- 除常量外,不公开任何字段
- 方法应该是抽象的或最终的
- 从超类构造函数不调用任何方法
...
所有这些对小型项目的影响要小于大型项目,对私人阶级的影响要小于公共项目。
查看其他答案。
人们常说,当以下句子为真时,一个类Bar
可以继承一个类Foo
:
- 酒吧是个傻瓜
不幸的是,仅上述测试是不可靠的。请改用以下内容:
- 酒吧是foo,并且
- 酒吧可以做foos可以做的一切。
第一测试确保了所有的吸气剂的Foo
在有意义Bar
(=共享属性),而在第二测试确保所有制定者的Foo
在有意义Bar
(=共享的功能)。
示例1:狗->动物
狗是动物,而狗可以做动物可以做的所有事情(例如呼吸,死亡等)。因此,该类Dog
可以继承该类Animal
。
示例2:圆形-/->椭圆
圆形是椭圆形,但圆形不能做椭圆形可以做的所有事情。例如,圆圈不能伸展,而椭圆可以伸展。因此,该类Circle
不能继承class Ellipse
。
这被称为Circle-Ellipse问题,这实际上不是问题,只是一个明确的证明,仅第一个测试不足以得出继承是可能的结论。尤其是,此示例强调了派生类应扩展基类的功能,而不是对其进行限制。否则,基类不能被多态使用。
即使可以使用继承也不意味着您应该:使用组合始终是一种选择。继承是一个功能强大的工具,允许隐式代码重用和动态分派,但是它确实有一些缺点,这就是为什么通常首选组合的原因。继承与组合之间的权衡并不明显,我认为最好在lcn的答案中加以解释。
根据经验,当预期多态使用非常普遍时,我倾向于选择继承而不是合成,在这种情况下,动态分派的功能可以使API更具可读性和优雅性。例如,Widget
在GUI框架中具有多态类,或者Node
在XML库中具有多态类,使得该API具有比纯粹基于组成的解决方案更易读和直观的API。
众所周知,另一种用于确定继承是否可能的方法称为Liskov替代原理:
使用指针或对基类的引用的函数必须能够使用派生类的对象而不知道它
本质上,这意味着如果可以多态使用基类,则继承是可能的,我认为这等效于我们的测试“ bar是foo,bar可以完成foos可以做的一切”。
computeArea(Circle* c) { return pi * square(c->radius()); }
。如果传递了一个椭圆,显然会损坏(radius()甚至意味着什么?)。椭圆不是圆形,因此不应从圆形派生。
computeArea(Circle *c) { return pi * width * height / 4.0; }
现在是通用的。
width()
和height()
?如果现在图书馆用户决定创建另一个名为“ EggShape”的类怎么办?是否也应源自“圆”?当然不是。卵形也不是圆形,椭圆形也不是圆形,因此任何一个都不应该从Circle派生,因为它破坏了LSP。在Circle *类上执行操作的方法对圆是什么有很强的假设,而打破这些假设几乎肯定会导致错误。
继承在子类和超类之间建立了牢固的关系。子类必须了解超类的实现细节。当您必须考虑如何扩展超级类时,创建超级类要困难得多。您必须仔细记录类不变式,并声明内部可重写方法使用哪些其他方法。
如果层次结构确实表示“是一个关系”,则继承有时会很有用。它涉及开放式封闭原则,该原则指出,应关闭类以进行修改,但可以扩展。这样,您就可以拥有多态性。具有处理超级类型及其方法的通用方法,但是通过动态调度,将调用子类的方法。这是灵活的,并有助于创建间接功能,这在软件中必不可少(以较少的了解实现细节)。
但是,继承很容易被过度使用,并且由于类之间的依赖关系而增加了复杂性。此外,由于层和方法调用的动态选择,很难理解在程序执行期间会发生什么。
我建议使用合成作为默认设置。它更具模块化,并具有后期绑定的优势(您可以动态更改组件)。另外,单独测试这些东西也更容易。而且,如果您需要使用类中的方法,则不必强制您采用某种形式(Liskov替换原理)。
Inheritance is sometimes useful... That way you can have polymorphism
是将继承和多态性的概念紧密联系在一起(假定给定上下文的子类型)。我的评论旨在指出您在评论中需要澄清的内容:继承不是实现多态性的唯一方法,实际上,不一定是在组成和继承之间做出决定的决定性因素。
假设飞机只有两个部分:引擎和机翼。
然后,有两种方法可以设计飞机等级。
Class Aircraft extends Engine{
var wings;
}
现在,您的飞机可以从拥有固定机翼开始,
然后随时将其更改为旋转机翼。它本质上
是带有翼的发动机。但是,如果我也想
即时更换引擎怎么办?
基类要么Engine
公开一个更改器以更改其
属性,要么将我重新设计Aircraft
为:
Class Aircraft {
var wings;
var engine;
}
现在,我也可以即时更换引擎。
您需要查看Bob叔叔的SOLID类设计原则中的Liskov替代原则。:)
这两种方式可以很好地生活在一起,并且实际上彼此支持。
合成只是模块化地发挥作用:您创建类似于父类的接口,创建新对象并委托对其进行调用。如果这些对象不需要彼此了解,那么它非常安全且易于使用。这里有很多可能性。
但是,如果父类出于某种原因需要为经验不足的程序员访问“子类”提供的功能,那么它似乎是使用继承的好地方。父类可以只调用它自己的抽象“ foo()”,该抽象被子类覆盖,然后可以将值提供给抽象基。
看起来不错,但是在很多情况下,最好给该类一个实现foo()的对象(甚至手动设置foo()所提供的值),而不是从某个需要继承的基类继承新类。要指定的函数foo()。
为什么?
因为继承是传递信息的不良方法。
这种组合在这里具有真正的优势:这种关系可以颠倒:“父类”或“抽象工作者”可以聚合实现特定接口的任何特定“子”对象+ 可以在任何其他类型的父内部设置任何子,它是type。并且可以有任意数量的对象,例如MergeSort或QuickSort可以对实现抽象Compare接口的对象列表进行排序。换一种说法:实现“ foo()”的任何对象组和可以利用具有“ foo()”的对象的其他对象组都可以一起玩。
我可以想到使用继承的三个真正原因:
如果这些是正确的,则可能有必要使用继承。
使用原因1并没有什么不好,在对象上具有可靠的接口是非常好的事情。如果此接口简单且不会更改,则可以使用组合或继承来完成此操作。通常,继承在这里非常有效。
如果原因是2,那就有点棘手了。您真的只需要使用相同的基类吗?通常,仅使用相同的基类是不够的,但这可能是您的框架的要求,这是无法避免的设计考虑。
但是,如果要使用私有变量(情况3),则可能会遇到麻烦。如果您认为全局变量不安全,则应考虑使用继承来访问也不安全的私有变量。提醒您,全局变量并不全是坏的-数据库本质上是一大组全局变量。但是,如果可以处理,那就很好了。
为了从不同的角度为新程序员解决此问题:
当我们学习面向对象的编程时,通常会在很早的时候就讲授继承,因此它被视为解决常见问题的简单方法。
我有三个类,都需要一些通用功能。因此,如果我编写一个基类并让它们全部继承自它们,那么它们都将具有该功能,而我只需要将其维护一次即可。
听起来不错,但实际上,由于以下几个原因之一,它几乎永远不会起作用:
最后,我们将代码束缚在一些棘手的问题上,并从中获得任何好处,只是说:“很酷,我了解了继承,现在我使用了它。” 这并不意味着居高临下,因为我们都已经做到了。但是我们所有人都这样做了,因为没有人告诉我们不要这样做。
有人向我解释“偏向于继承而不是继承”时,我回想起我每次尝试使用继承在类之间共享功能时意识到,在大多数情况下,它并不能很好地工作。
解毒剂是单一责任原则。将其视为约束。我班必须做一件事。我必须能够给我的班级一个名称,以某种方式描述它所做的一件事情。(所有情况都有例外,但是在学习时,绝对规则有时会更好。)因此,我无法编写名为的基类ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses
。无论我需要什么独特的功能,都必须在其自己的类中,然后其他需要该功能的类可以依赖于该类,而不是从该类继承。
冒着过于简化的风险,这就是组合-组成多个类一起工作。一旦养成了这种习惯,我们就会发现它比使用继承要灵活,可维护和可测试得多。
当你有一个是,一个两个类(例如狗是犬科动物)之间的关系,你去继承。
另一方面,当您在两个班级(学生开设课程)或(老师学习课程)之间具有某种或某种形容词关系时,您选择了作文。
一种简单的理解方法是,当您需要类的对象具有与其父类相同的接口时,应使用继承,以便可以将其视为父类的对象(向上投射)。 。此外,对派生类对象的函数调用在代码中的所有地方都将保持不变,但是具体的调用方法将在运行时确定(即,低级实现不同,高级接口保持不变)。
当您不需要新类具有相同的接口时,即您希望隐藏该类的用户不需要了解的类实现的某些方面时,应使用组合。因此,组合更多地是通过支持封装(即隐藏实现)的方式,而继承是为了支持抽象(即提供某种事物的简化表示,在这种情况下,对于具有不同内部结构的各种类型,使用相同的接口)。
继承是代码重用的一种非常强大的机制。但是需要正确使用。我要说的是,如果子类也是父类的子类型,则可以正确使用继承。如上所述,李斯科夫替代原则是这里的重点。
子类与子类型不同。您可能会创建不是子类型的子类(这是您应该使用组合的时候)。要了解什么是子类型,让我们开始解释什么是类型。
当我们说数字5是整数类型时,我们说的是5属于一组可能的值(例如,请参阅Java基本类型的可能的值)。我们还指出,我可以对一组值执行有效的方法,例如加法和减法。最后我们要说明的是,有一组属性总是可以满足的,例如,如果我将值3和5相加,结果将是8。
再举一个例子,考虑一下抽象数据类型,即整数集和整数列表,它们可以保存的值限制为整数。它们都支持一组方法,例如add(newValue)和size()。它们都具有不同的属性(类不变),Set不允许重复,而List允许重复(当然,它们都满足其他属性)。
子类型也是一种类型,它与另一个类型有关系,称为父类型(或超类型)。子类型必须满足父类型的特征(值,方法和属性)。该关系意味着在期望超类型的任何上下文中,它都可以被子类型替代,而不会影响执行的行为。让我们来看一些代码来举例说明我的意思。假设我写了一个整数列表(用某种伪语言):
class List {
data = new Array();
Integer size() {
return data.length;
}
add(Integer anInteger) {
data[data.length] = anInteger;
}
}
然后,我将整数集写为整数列表的子类:
class Set, inheriting from: List {
add(Integer anInteger) {
if (data.notContains(anInteger)) {
super.add(anInteger);
}
}
}
我们的整数集类是整数列表的子类,但不是子类型,因为它不能满足List类的所有功能。满足方法的值和签名,但不满足属性。add(Integer)方法的行为已明显更改,没有保留父类型的属性。从您的班级客户的角度考虑。他们可能会收到一组整数,其中应有一个整数列表。客户端可能想要添加一个值并将该值添加到列表中,即使该值已存在于列表中。但是,如果值存在,她将不会得到这种行为。给她一个很大的惊喜!
这是不当使用继承的经典示例。在这种情况下,请使用合成。
(来自:正确使用继承的片段)。
根据我的经验,继承关系为“是”时应使用继承,继承关系为“必须”时应使用组合。即使这样,我仍然认为您应该始终倾向于合成,因为它消除了很多复杂性。
组成与继承继承是一个广泛的主题。对于更好的解决方案,没有真正的答案,因为我认为这完全取决于系统的设计。
通常,对象之间的关系类型会提供更好的信息以选择其中之一。
如果关系类型为“ IS-A”关系,则继承是更好的方法。否则,关系类型为“ HAS-A”关系,则组合会更好。
它完全取决于实体关系。
即使首选Composition,我还是要强调继承的优点和Composition的缺点。
继承的优点:
它建立逻辑“ IS A”关系。如果汽车和卡车是车辆的两种类型(基础类),则子类IS A基本类。
即
汽车是车辆
卡车是一辆车
通过继承,您可以定义/修改/扩展功能
组成的缺点:
例如,如果 Car包含Vehicle,并且您必须获取在Vehicle中定义的Car的价格,则您的代码将如下所示
class Vehicle{
protected double getPrice(){
// return price
}
}
class Car{
Vehicle vehicle;
protected double getPrice(){
return vehicle.getPrice();
}
}
正如许多人所说,我首先要进行检查-是否存在“是”关系。如果存在,我通常会检查以下内容:
基类是否可以实例化。也就是说,基类是否可以是非抽象的。如果不是抽象的话,我通常更喜欢构图
例如1.会计师是一名雇员。但是我不会使用继承,因为可以实例化Employee对象。
例如2.书是 SellingItem。SellingItem无法实例化-这是抽象概念。因此,我将使用Inheritacne。SellingItem是一个抽象基类(或接口) C#中的)
您如何看待这种方法?
此外,我支持@anon回答“ 为什么要完全使用继承?”中的内容。
使用继承的主要原因不是作为一种组合形式,而是可以获取多态行为。如果不需要多态性,则可能不应该使用继承。
@MatthieuM。在/software/12439/code-smell-inheritance-abuse/12448#comment303759_12448中说
继承的问题在于它可以用于两个正交的目的:
接口(用于多态)
实现(用于代码重用)
参考