为什么更喜欢组合而不是继承?每种方法都有哪些权衡?什么时候应该选择继承而不是组合?
当前回答
继承是一种非常强大的代码重用机制。但需要正确使用。如果子类也是父类的子类型,我会说继承是正确使用的。如上所述,利斯科夫替代原则是这里的关键点。
子类与子类型不同。您可以创建不是子类型的子类(此时应该使用组合)。为了理解什么是子类型,让我们开始解释什么是类型。
当我们说数字5是整数类型时,我们说明5属于一组可能的值(例如,请参阅Java原语类型的可能值)。我们还声明,我可以对值执行一组有效的方法,如加法和减法。最后,我们要说明的是,有一组财产总是可以满足的,例如,如果我将值3和5相加,结果会得到8。
举另一个例子,考虑抽象数据类型,整数集合和整数列表,它们可以保存的值仅限于整数。它们都支持一组方法,如add(newValue)和size()。而且它们都有不同的财产(类不变量),Sets不允许重复,而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);
}
}
}
我们的整数集合类是整数列表的子类,但不是子类型,因为它不满足列表类的所有特性。满足了方法的值和签名,但不满足财产。add(Integer)方法的行为已经明显改变,没有保留父类型的财产。从类的客户端的角度思考。他们可能会收到一组整数,其中需要一个整数列表。客户端可能希望添加一个值,并将该值添加到列表中,即使该值已经存在于列表中。但如果价值存在,她就不会有这种行为。她大吃一惊!
这是一个不正确使用继承的典型例子。在这种情况下使用合成。
(片段来自:正确使用继承)。
其他回答
你想强迫自己(或其他程序员)坚持什么,什么时候你想让自己(或另一个程序员)有更多的自由。有人认为,当你想强迫某人以某种方式处理/解决某个特定问题时,继承是有帮助的,这样他们就不会走错方向。
Is-a和Has-a是一个有用的经验法则。
你需要看看Bob叔叔的SOLID类设计原则中的Liskov替换原则。:)
除了是/有一个考虑因素之外,还必须考虑对象必须经历的继承的“深度”。任何超过五或六级继承深度的内容都可能会导致意外的强制转换和装箱/拆箱问题,在这种情况下,编写对象可能是明智的。
我同意@帕维尔的说法,他说,有地方可以组成,也有地方可以继承。
我认为如果你的答案是肯定的,那么应该使用继承。
您的类是从多态性中受益的结构的一部分吗?例如,如果您有一个Shape类,它声明了一个名为draw()的方法,那么我们显然需要Circle和Square类作为Shape的子类,这样它们的客户端类将依赖于Shape而不是特定的子类。您的类是否需要重用其他类中定义的任何高级交互?如果没有继承,模板方法设计模式将不可能实现。我相信所有可扩展框架都使用这种模式。
然而,如果您的意图纯粹是代码重用,那么组合很可能是更好的设计选择。
继承是非常强大的,但你不能强迫它(参见:圆-椭圆问题)。如果你真的不能完全确定一个真正的“is-a”子类型关系,那么最好是组合。