C++ 继承或组合:依靠;is-a";及;有-a“吗;?

C++ 继承或组合:依靠;is-a";及;有-a“吗;?,c++,inheritance,oop,C++,Inheritance,Oop,当我设计类并且必须在继承和组合之间进行选择时,我通常使用经验法则:如果关系是“is-a”,则使用继承,如果关系是“has-a”,则使用组合 它总是正确的吗 谢谢。没有。如果关系为“has-a”,则使用合成。(补充:问题的原始版本说“使用继承”两个字——一个简单的打字错误,但更正将我答案的第一个字从“否”改为“是”。) 并默认使用构图;仅在必要时使用继承(但不要犹豫使用它)。是和否 这条线可能会模糊。从OO的早期就有一些非常糟糕的OO编程例子,比如:经理就是员工就是人 关于继承,您必须记住:继承破

当我设计类并且必须在继承和组合之间进行选择时,我通常使用经验法则:如果关系是“is-a”,则使用继承,如果关系是“has-a”,则使用组合

它总是正确的吗

谢谢。

没有。如果关系为“has-a”,则使用合成。(补充:问题的原始版本说“使用继承”两个字——一个简单的打字错误,但更正将我答案的第一个字从“否”改为“是”。)

并默认使用构图;仅在必要时使用继承(但不要犹豫使用它)。

是和否

这条线可能会模糊。从OO的早期就有一些非常糟糕的OO编程例子,比如:经理就是员工就是人

关于继承,您必须记住:继承破坏了封装。继承是一个实现细节。关于这个问题有各种各样的文章

最简单的概括是:

喜欢构图。


这并不意味着使用它来完全排除继承。这只是意味着继承是一种退路。

我不认为它像“is-a”和“has-a”那么简单——正如克莱特斯所说,这条线可能会变得非常模糊。这并不是说两个人总是得出相同的结论,只是作为一个指导

同样正如克莱特斯所说,他更喜欢构图而不是继承。在我看来,从基类派生必须有很好的理由,特别是基类确实需要为继承而设计,并且确实需要为您想要应用的专门化而设计


除此之外-这可能是一个不受欢迎的想法-它归结为味道和直觉。随着时间的推移,我认为优秀的开发人员会更好地了解继承何时起作用,何时不起作用,但他们可能会发现很难清楚地表达出来。我知道我发现这很难,正如这个答案所表明的那样。也许我只是把困难投射到其他人身上:)

继承并不总是意味着存在“是-是”的关系,缺乏这种关系并不总是意味着没有

示例:

  • 如果您使用受保护的或私有的 那你就失去了外部的继承权 可替代性,即 你的等级之外的任何人都是 有关的派生类型不是 基地
  • 如果您重写了一个基本虚拟成员,并更改了从引用该基本成员的人、从原始基本实现中可以观察到的行为,那么您实际上破坏了可替换性。即使你有公共继承权,继承权也不是基础
  • 您的类有时可以替代另一个类,而不需要任何继承关系。例如,如果要为适当的常量成员函数使用模板和相同的接口,则椭圆恰好是完全圆形的“椭圆常量&”可以替换为“圆形常量&”

我想说的是,无论你是否使用继承,要在两种类型之间保持一种“是-是”的替代关系都需要大量的思考和工作。

人们常说继承是一种“是-是”的关系,但这会给你带来麻烦。C++中的继承分为两种思想:代码重用和定义接口。 第一个说“我的类与另一个类类似。我将只为它们之间的增量编写代码,并尽可能重用另一个类中的其他实现。”

第二个依赖于抽象基类,是类承诺实现的方法列表

第一种方法非常方便,但如果做得不好,也会导致维护问题,因此有些人甚至会说你永远不应该这样做。大多数人强调第二点,对其他语言明确称为接口的东西使用继承。

我的经验法则(虽然不是具体的)是,如果我可以对不同的类使用相同的代码,那么我将该代码放入父类并使用继承。否则我就用构图

这使得我编写的代码更少,而且本质上更易于维护

如果关系是“is-a”,则使用继承,如果关系是“has-a”,则使用组合。 它总是正确的吗

从某种意义上说,是的。但你必须小心,不要引入不必要的、人为的“is-a”关系

例如,有人可能认为ThickBordedRectangle是一个矩形,乍一看似乎是合理的,并决定使用继承。但是这种情况更好的描述是一个矩形有一个边框,它可能是也可能不是一个厚边框。在这种情况下,他更喜欢作曲

同样,人们可能会认为ThickBorder是一种特殊的边界,并使用继承;但最好说边框有宽度,因此更喜欢构图

在所有这些模棱两可的情况下,我的经验法则是三思而后行,正如其他人建议的那样,我更喜欢组合而不是继承。

No-“是a”并不总是导致继承。一个被广泛引用的例子是正方形和矩形之间的关系。正方形是矩形,但设计从矩形类继承正方形类的代码是不好的

我的建议是增强您的“is a/has a”启发式。要检查继承关系是否符合Liskov替换原则,请询问基类的客户端是否可以在不知道它正在子类上操作的情况下对该子类进行操作。当然,必须保留子类的所有属性

在square/rectangle示例中,我们必须询问rectangle的客户机是否可以在不知道正方形的情况下对正方形进行操作。客户机必须知道的是,它正在矩形上运行。以下
void g(Rectangle& r)
{
    r.SetWidth(5);
    r.SetHeight(4);
    assert(r.GetWidth() * r.GetHeight()) == 20);
}