Oop 继承能被合成完全取代吗?
这个问题是而不是像“继承与构成”这样的问题 我完全理解继承与组合的区别,我知道它们的优点和缺点,而且这两个概念似乎都很简单。但是关于继承和组合的问题太多了,我想,也许我误解了这个简单的想法 让我们集中精力。Go是一种来自Google的语言,每个人都很兴奋它没有继承,没有类,但它有组合,这很酷。 对我来说,Go中的组合为您提供了与其他语言(C++,Java,…)中的继承完全相同的功能。组件方法自动公开,并作为后续结构的方法提供,如下所示:Oop 继承能被合成完全取代吗?,oop,design-patterns,inheritance,language-agnostic,composition,Oop,Design Patterns,Inheritance,Language Agnostic,Composition,这个问题是而不是像“继承与构成”这样的问题 我完全理解继承与组合的区别,我知道它们的优点和缺点,而且这两个概念似乎都很简单。但是关于继承和组合的问题太多了,我想,也许我误解了这个简单的想法 让我们集中精力。Go是一种来自Google的语言,每个人都很兴奋它没有继承,没有类,但它有组合,这很酷。 对我来说,Go中的组合为您提供了与其他语言(C++,Java,…)中的继承完全相同的功能。组件方法自动公开,并作为后续结构的方法提供,如下所示: 主程序包 进口( “fmt” ) 类型汽车结构{ 名称字符
主程序包
进口(
“fmt”
)
类型汽车结构{
名称字符串
}
func(c*Car)move()bool{
返回真值
}
类型MyCar结构{
汽车
}
func main(){
var c MyCar
fmt.Print(c.move())
}
综上所述,组合比继承更好,因为:
<强>并且如果你考虑Go及其接口(每个对象,都有一个接口定义的方法,实现这个接口含义),你有最终的解决方案吗? 我们是否可以说,含有一些语法糖分的组合可以取代继承?
这样的设计符合Liskov替换原则。我是否错过了一些东西,或者继承(从任何语言中都知道)与从Go中知道的组合(和接口)相比没有优势 ====edit1===== 为了澄清,可以在Go
中使用“标准”合成机制,如下所示(此示例的行为与前一示例类似):
但是,如果您像在上一个示例中那样使用它,那么所有的方法都可以隐式地“在MyCar类中”使用。简短的回答
它真的没有那么黑和白。简言之,是的。任何可以通过继承解决的情况都可以通过组合来解决。所以简言之,你的问题的答案是肯定的;继承可以被组合所取代
为什么没那么简单
何时使用继承这不是你是否可以交换它们的问题。这取决于你在其中编程的上下文,而更多的是你是否应该交换它们的问题。以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都能做,所以我们断言Dave是一个“人”是有道理的
何时使用合成当我们只想公开类接口的一部分时,我们使用组合。按照前面的示例,假设
Dave
有一个吉他
。吉他有一个更复杂的接口:
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
有调谐吗?我想没有!这就是继承没有意义的地方。我们不想将整个吉他
界面与Dave
界面一起公开。我们只希望用户能够访问Dav的内容e
需要访问,因此在这种情况下,我们将使用一些组合:
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.
}
结论
这并不是什么可以取代另一个。这是关于你在其中实现技术的情况。希望在本例结束时,你会看到继承适用于一个对象是一个对象的情况,当一个对象有一个对象时使用组合。问得好
当你想在语义上区分“A是AA B”和“A有AA B”时,你可能更喜欢继承而不是组合。例如,类别汽车可能有一个发动机成员(组合),但可能是车辆(即从车辆继承)
我认为区别在于必须维护两个类共享的方法的数量。继承时,只需重写基类中真正需要的(虚拟)函数;如果要使用纯合成,则需要重新实现成员类中的每个公共方法
最终,“最佳”方法是最适合您当前应用程序的方法
编辑:
这里有一个简短的辩论:
以及维基百科关于它的一篇文章(!):
很抱歉发布这篇文章,但问题和答案似乎都集中在代码重用上。我们可以使用继承和组合来重用来自共同“祖先”的一些代码,这是真的。在这里,组合效果更好
但是继承主要不是为了代码重用,而是为了动态绑定。实际上,可以将“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一无所知,但我正确地理解了您的话,它提供了另一种定义“此对象实现此接口”的方法:
每个对象都有一个接口定义的方法,实现这个i
// 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();
}
}