继承还是构成:依靠“is-a”和“has-a”?

时间:2009-01-17 18:08:45

标签: c++ inheritance oop

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

总是对的吗?

谢谢。

9 个答案:

答案 0 :(得分:35)

不 - “是一个”并不总是导致遗传。引用得很好的例子是正方形和矩形之间的关系。正方形是一个矩形,但设计一个从Rectangle类继承Square类的代码是不好的。

我的建议是使用Liskov Substitution Principle增强您的“是一个/有一个”启发式。要检查继承关系是否符合Liskov替换原则,请询问基类的客户端是否可以在子类上运行,而不知道它在子类上运行。当然,必须保留子类的所有属性。

在正方形/矩形示例中,我们必须询问矩形客户端是否可以在正方形上运行而不知道它是正方形。客户必须知道的是它在矩形上运行。以下函数演示了一个客户端,它假定设置矩形的宽度会使高度保持不变。

void g(Rectangle& r)
{
    r.SetWidth(5);
    r.SetHeight(4);
    assert(r.GetWidth() * r.GetHeight()) == 20);
}

这个假设适用于矩形,但不适用于正方形。因此该函数不能在一个正方形上运行,因此继承关系违反了Liskov Substitution原则。 (顺便说一下 - 示例来自dead linkOther examples

答案 1 :(得分:8)

是和否。

线条可能模糊不清。从OO早期的OO编程的一些非常糟糕的例子来看,这没有得到帮助:经理是员工是人。

你必须记住的关于继承的事情是:继承打破了封装。继承是一个实现细节。有关这个主题的各种文章。

最简单的总结方法是:

喜欢作文。

这并不意味着将它用于完全排除继承。这只意味着继承是一种后备的立场。

答案 2 :(得分:5)

  

如果关系是“is-a” - 使用继承,如果关系是“has-a” - 使用组合。   总是对的吗?

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

例如,有人可能认为ThickBorderedRectangle 是-a Rectangle,初看起来似乎很合理,并决定使用继承。但是这种情况更好地描述了一个Rectangle 有一个边框,它可能是也可能不是ThickBorder。在这种情况下,他更喜欢作文。

以同样的方式,人们可能会认为ThickBorder 是一个特殊边框并使用继承;但是最好说边框有一个宽度,因此更喜欢合成。

在所有这些模棱两可的案例中,我的经验法则是三思而后行,并且正如其他人所建议的那样,更喜欢构成而不是继承

答案 3 :(得分:3)

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

另外正如克莱图斯所说,更喜欢构图而不是继承。在我看来,必须有一个很好的理由从基类派生 - 特别是,基类确实需要设计进行继承,实际上对于你想要的那种专业化申请。

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

答案 4 :(得分:2)

继承并不总是意味着存在“is-a”关系,缺少一个并不总是意味着没有。

示例:

  • 如果您使用受保护或私有 继承然后你失去外部 可替代性,即 你的等级之外的任何人都是 关注衍生不是一种类型 基。
  • 如果覆盖基本虚拟成员并从引用Base的人那里改变可观察行为,则从原始Base实现中,您实际上已经破坏了可替代性。即使你有一个公共继承,但heirarchy Derived is-not-a Base。
  • 你的班级有时可以替代另一个人在工作中没有任何继承关系。例如,如果您要为适当的const成员函数使用模板和相同的接口,那么“Ellipse const&” Ellipse恰好是圆形的,可以替代“Circle const&”。

我想在这里说的是,无论你是否使用继承,在两种类型之间维持“is-a”可替代关系需要花费很多心思和工作。

答案 5 :(得分:1)

<德尔>没有是。如果关系是“has-a”,请使用合成。 (添加:问题的原始版本说“使用继承”两者 - 一个简单的拼写错误,但是更正将我的答案的第一个单词从“否”改为“是”。)

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

答案 6 :(得分:1)

人们常说继承是一种“是一种”关系,但这会让你陷入困境。 C ++中的继承分为两个概念:代码重用和定义接口。

第一个说“我的班级就像其他班级一样。我只是为他们之间的三角洲编写代码,并尽可能多地重用其他班级的其他实现。”

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

第一个可以非常方便,但如果做得不好也会导致维护问题,所以有些人会说你永远不会这样做。大多数人都强调第二种,使用继承来解释其他语言明确称之为接口的问题。

答案 7 :(得分:1)

我认为约翰有正确的想法。您将您的班级关系视为创建模型,这是他们开始教您OOP校长时他们试图让您做的事情。真的,你最好根据代码 architecture 查看事物的运作方式。也就是说,对于其他程序员(或您自己)来说,最好的方法是在不破坏代码的情况下重用代码,或者必须查看实现以了解它的作用。

我发现继承经常打破“黑匣子”的理想,所以如果信息隐藏很重要,那么它可能不是最好的方法。我越来越发现,继承对于提供通用接口比在其他地方重用组件更好。

当然,每种情况都是独特的,没有正确的方法来做出这个决定,只有经验法则。

答案 8 :(得分:-2)

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

这使我编写的代码更少,本质上更容易维护。

相关问题