【发布时间】:2010-10-02 00:14:32
【问题描述】:
当我设计类并且必须在继承和组合之间进行选择时,我通常使用经验法则:如果关系是“is-a” - 使用继承,如果关系是“has-a” - 使用组合.
总是正确的吗?
谢谢。
【问题讨论】:
标签: c++ inheritance oop
当我设计类并且必须在继承和组合之间进行选择时,我通常使用经验法则:如果关系是“is-a” - 使用继承,如果关系是“has-a” - 使用组合.
总是正确的吗?
谢谢。
【问题讨论】:
标签: c++ inheritance oop
不——“是一个”并不总是导致继承。一个被广泛引用的例子是正方形和矩形之间的关系。正方形就是长方形,但是设计从 Rectangle 类继承 Square 类的代码会很糟糕。
我的建议是使用Liskov Substitution Principle 来增强您的“是/具有”启发式。要检查继承关系是否符合里氏替换原则,请询问基类的客户端是否可以在不知道它正在操作子类的情况下对子类进行操作。当然,必须保留子类的所有属性。
在正方形/矩形的例子中,我们必须要问一个矩形的客户端是否可以在不知道它是正方形的情况下对正方形进行操作。客户必须知道的只是它是在一个矩形上操作的。下面的函数演示了一个假设设置矩形宽度保持高度不变的客户端。
void g(Rectangle& r)
{
r.SetWidth(5);
r.SetHeight(4);
assert(r.GetWidth() * r.GetHeight()) == 20);
}
这个假设适用于矩形,但不适用于正方形。所以函数不能在正方形上运行,因此继承关系违反了里氏替换原则。
【讨论】:
是和不是。
这条线可以模糊。 OO 早期的一些非常糟糕的 OO 编程示例并没有帮助解决这个问题,例如:Manager is an Employee is an Person。
关于继承,您必须记住的一点是:继承破坏了封装。继承是一个实现细节。关于这个主题有各种各样的文章。
最简洁的总结方式是:
喜欢构图。
这并不意味着使用它来完全排除继承。这只是意味着继承是一个后备位置。
【讨论】:
如果关系是“is-a” - 使用继承,如果关系是“has-a” - 使用组合。 总是正确的吗?
从某种意义上说,是的。但您必须注意不要引入不必要的、人为的“is-a”关系。
例如,有人可能认为ThickBorderedRectangle 是一个 Rectangle,乍一看似乎很合理,并决定使用继承。但是这种情况更好地描述为 Rectangle has-a Border,它可能是也可能不是 ThickBorder。在这种情况下,他会更喜欢作曲。
以同样的方式,人们可能认为厚边框是一个特殊的边框并使用继承;但最好说边框 has-a 宽度,因此更喜欢构图。
在所有这些模棱两可的情况下,我的经验法则是三思而后行,并且正如其他人建议的那样,更喜欢组合而不是继承。
【讨论】:
我认为这并不像“is-a”与“has-a”那么简单——正如 cletus 所说,这条线会变得非常模糊。仅仅作为一个指导原则,两个人并不总是会得出相同的结论。
正如 cletus 所说,更喜欢组合而不是继承。在我看来,必须有充分的理由从基类派生 - 特别是,基类确实需要设计 用于继承,并且确实需要用于您想要的那种专业化申请。
除此之外 - 这可能是一个不受欢迎的想法 - 它归结为品味和直觉。随着时间的推移,我认为优秀的开发人员会更好地了解继承何时有效,何时无效,但他们可能会发现很难清楚地表达出来。我知道 我 觉得很难,正如这个答案所表明的那样。也许我只是将困难投射到其他人身上:)
【讨论】:
继承并不总是意味着存在“is-a”关系,缺少继承并不总是意味着不存在。
例子:
我在这里想说的是,无论您是否使用继承,在两种类型之间维持“is-a”可替代关系都需要大量的思考和工作。
【讨论】:
否是。如果关系是“has-a”,则使用组合。 (添加:问题的原始版本对两者都说“使用继承” - 一个简单的错字,但更正将我的答案的第一个单词从“否”更改为“是”。)
并且默认使用合成;仅在必要时使用继承(但不要犹豫使用它)。
【讨论】:
人们经常说继承是一种“is-a”关系,但这会给您带来麻烦。 C++ 中的继承分为两种思路:代码重用和定义接口。
第一个说“我的类和其他类一样。我将为它们之间的增量编写代码,并尽可能多地重用其他类的其他实现。”
第二个依赖于抽象基类,是类承诺实现的方法列表。
第一个可能非常方便,但如果做得不好也会导致维护问题,因此有些人甚至会说你不应该这样做。大多数人强调第二个,将继承用于其他语言显式调用的接口。
【讨论】:
我认为约翰的想法是正确的。您将班级关系视为创建模型,这是他们在开始教您 OOP 原则时试图让您做的事情。确实,您最好根据代码架构 来了解事物的功能。也就是说,对于其他程序员(或您自己)来说,重用您的代码而不破坏它或不必查看实现以了解它的作用的最佳方式是什么。
我发现继承经常打破“黑匣子”的理想,所以如果信息隐藏很重要,它可能不是最好的方法。我越来越多地发现继承对于提供通用接口比在其他地方重用组件更好。
当然,每种情况都是独一无二的,没有正确的方法来做出这个决定,只有经验法则。
【讨论】:
我的经验法则(虽然不是具体的)是,如果我可以为不同的类使用相同的代码,那么我会将这些代码放入父类并使用继承。否则我会使用合成。
这使我可以编写更少的代码,而这本质上更容易维护。
【讨论】: