这个问题不问题,如“遗产与作曲”。
我完全理解继承与组合的区别,我知道Liskov substitution principle,diamond problem,它们的优点和缺点,这两个概念似乎都很简单。但到处都有关于继承和构成的问题,我想,也许我误解了这个简单的想法。
让我们关注Go。 Go是一种来自谷歌的语言,每个人都很兴奋它没有继承,它没有类,但它有组成,这很酷。 对我来说,Go中的组合为您提供完全与其他语言(C ++,Java,...)中的继承相同的功能 - 组件方法自动公开并可用作后续结构的方法,例如此处:
package main
import (
"fmt"
)
type Car struct{
name string
}
func (c *Car) move() bool {
return true
}
type MyCar struct{
Car
}
func main() {
var c MyCar
fmt.Print(c.move())
}
所以总结一下,组合比继承更好,因为:
如果您考虑Go及其接口(每个对象,具有接口定义的方法,实现此接口隐含),您是否拥有最终解决方案? 我们可以说含有一些语法糖的成分可以取代遗传吗?
此类设计符合Liskov替代原则。我是否会遗漏某些内容或继承(从任何语言中获知)与Go中已知的组合(和接口)相比没有优势?
===== edit1 =====
为了澄清,在Go
中可以使用“标准”组合机制,就像这样(这个例子的行为与前一个一样):
package main
import (
"fmt"
)
type Car struct{
name string
}
func (c *Car) move() bool {
return true
}
type MyCar struct{
car Car
}
func (c *MyCar) move() bool {
return c.car.move()
}
func main() {
var c MyCar
fmt.Print(c.move())
}
但是如果你像前面的例子一样使用它,那么所有方法都可以在MyCar类中使用。
答案 0 :(得分:4)
它真的不像那样黑白。简而言之,是的。任何可以通过继承解决的情况都可以通过组合来解决。简而言之,你的问题的答案是肯定的;继承可以用组合代替。
何时使用继承
这不是你可以换掉它们的问题。这取决于你编程的上下文,而且更多的是你是否应该将它们交换出来的问题。以Java为例:
public class Person
{
// Assume every person can speak.
public void speak()
{
}
}
现在,让我们说我们有另一个班,戴夫。戴夫是一个人。
public class Dave extends Person
{
public void speak() { System.out.println("Hello!"); }
public void killSomeone() {} // Dave is a violent Guy.
}
现在,课程Dave
看起来更合理吗?
public class Dave
{
private Person p;
// Composition variant.
public void speak() { p.speak(); }
public void killSomeone() {} // Dave is a violent Guy.
}
此代码暗示Dave有一个人。它不是那么简单,也不能解释自己。此外,任何人都可以做,Dave可以做,所以我们断言戴夫是一个“人”是有意义的。
何时使用作文
当我们只想暴露部分类的界面时,我们使用Composition。按照上一个示例,假设Dave
有一个Guitar
。吉他的界面更复杂:
public class Guitar
{
public Color color;
// Guitar's color.
public Tuning tuning;
// Guitar's tuning.
public void tuneGuitar()
{}
public void playChord()
{}
public void setColor()
{}
}
现在,如果我们要继承这个类,结果会是什么?
嗯,Dave类现在有color
和tuning
属性。 Dave
有调整吗?我想不是!这是继承毫无意义的地方。我们不希望将整个Guitar
接口与Dave
接口一起公开。我们只希望用户能够访问Dave
需要访问的内容,因此在这种情况下我们会使用一些组合:
public class Dave extends Person
{
private Guitar guitar;
// Hide the guitar object. Then limit what the user can do with it.
public void changeGuitarColor(Color newColor)
{
// So this code makes a lot more sense than if we had used inheritance.
guitar.setColor(newColor);
}
public void speak() { System.out.println("Hello!"); }
public void killSomeone() {} // Dave is a violent Guy.
}
这真的不是什么可以取代另一个。这是关于你正在实现这些技术的情况。希望在示例结束时你会看到继承适用于一个对象IS A
对象和一个对象{{1}时使用组合的情况。对象。
答案 1 :(得分:3)
对于necroposting很抱歉,但似乎问题和答案都集中在代码重用上。我们可以使用继承和组合来重用来自常见“祖先”的一些代码,这是真的。而且这里的作文效果更好。
但继承主要不是代码重用,而是动态绑定。 “is a”关系实际上可以如下表达:调用代码使用对象的接口,对特定实现一无所知。
伪代码:
// Interface
class A {
drawMe() {} ;
};
// Implementations
class Rect:A {
drawMe() {DrawRect();};
};
class Circle:A {
drawMe() {Drawcircle();};
};
main() {
// We fill the array in one part of the program
Array_of_A arr;
arr.add(new Rect);
arr.add(new Circle);
// We can use the array in completely another part
// without any idea of what the items really are,
// only know the interface
foreach(item from arr) {
item->drawMe();
}
}
在许多语言中(例如像C ++),继承是说“此对象实现此接口”的唯一实用方法。而且它还允许做一些像代码重用这样的事情,这些事情最好用组合来完成。
我对Go一无所知,但我理解你的话,它提供了另一种定义“此对象实现此接口”的方法:
每个具有接口定义的方法的对象都实现了这个界面隐含的
因此,如果您也可以通过接口调用对象,那么它将完成继承工作。但它还允许替换基类,这就是为什么我认为它们称之为组合。也许是因为它以不同于vtable的某种方式在内部实现,但我无法想到它可以产生什么不同。
因此,如果您找到说“此对象实现此接口”的方法,继承可以由合成替换。
答案 2 :(得分:2)
好问题......
当你想要在“A 是 a B”和“A 有 a B”之间进行语义区分时,你可能希望更喜欢继承而非组合。例如。 class Car可以拥有引擎成员(组合),但可以 (即从其继承)车辆。
我认为必须保持两个类共享的方法数量之间存在区别。当你继承时,你只需要覆盖你真正需要的基类中的那些(虚拟)函数;如果你要使用纯粹的合成,你需要重新实现你公开的成员类的每个方法。
最终,“最佳”方法是最适合您当前应用的方法。
编辑:
这里有一个小而简洁的辩论: http://www.sitepoint.com/forums/showthread.php?568744-When-to-use-inheritance
以及关于它的维基百科文章(!): http://en.wikipedia.org/wiki/Composition_over_inheritance#Drawbacks
答案 3 :(得分:0)
我认为真正的问题是继承与多态。在Java之类的流行语言中,两者是结合在一起的,即子类型多态性只能通过实现继承来实现。 Go 证明即使在静态类型的语言中,多态性也无需继承即可实现(这在Go之前是已知的)。所以问题是,如果您可以不继承而拥有多态性,为什么要具有继承性呢?
现代的答案似乎是……您不会。将继承与多态性混合起来会假定继承是最常见的用例。甚至James Gosling也承认implementation inheritance was a mistake。但这并不是说它没有用。但是继承的用例可以由框架或DSL覆盖,而不必强加于每个子类型关系中。在这方面, Go 有机会向Java的错误学习。