使用多重继承是一个好概念还是我可以做其他事情?
原文由 Hai 发布,翻译遵循 CC BY-SA 4.0 许可协议
我们使用埃菲尔。我们有出色的 MI。不用担心。没有问题。轻松管理。有时不使用 MI。然而,它比人们意识到的更有用,因为他们是:A)使用一种不能很好地管理它的危险语言 - 或 - B)对他们多年来围绕 MI 工作的方式感到满意 - 或 - C)其他原因(太多了,无法列出我很确定——请参阅上面的答案)。
对我们来说,使用 Eiffel,MI 和其他任何东西一样自然,是工具箱中的另一个好工具。坦率地说,我们并不担心没有其他人在使用 Eiffel。不用担心。我们对我们拥有的东西很满意,并邀请您来看看。
在您查看时:特别注意 Void 安全性和消除 Null 指针取消引用。当我们都在 MI 周围跳舞时,您的指针正在丢失! :-)
原文由 Liberty Lover 发布,翻译遵循 CC BY-SA 3.0 许可协议
3 回答2k 阅读✓ 已解决
2 回答3.9k 阅读✓ 已解决
2 回答3.2k 阅读✓ 已解决
1 回答3.2k 阅读✓ 已解决
1 回答2.7k 阅读✓ 已解决
3 回答3.4k 阅读
1 回答1.6k 阅读✓ 已解决
多重继承(缩写为 MI)有 _味道_,这意味着 _通常_,它是出于不好的原因而完成的,并且会在维护者面前反击。
概括
1. 也许作文?
这对于继承来说是正确的,因此对于多重继承来说更是如此。
您的对象真的需要从另一个对象继承吗?
Car
不需要继承Engine
来工作,也不需要继承Wheel
。一个Car
有一个Engine
和四个Wheel
。如果您使用多重继承而不是组合来解决这些问题,那么您做错了什么。
2. 恐惧钻石
通常,您有一个类
A
,然后是B
和C
都继承自A
。然后(不要问我为什么)有人决定D
必须从B
和C
继承。八年来我遇到过两次这样的问题,这很有趣,因为:
D
不应该继承自B
和C
) (事实上,C
根本不应该存在……)A
在其孙类D
--- 中出现了两次,因此更新了一个父字段A::field
意味着要么更新它两次(通过B::field
和C::field
),或者稍后出现错误并崩溃(在B::field
中新建一个指针,然后删除C::field
…)如果这不是您想要的,在 C++ 中使用关键字 virtual 来限定继承可以避免上述双重布局,但无论如何,根据我的经验,您可能做错了什么……
在对象层次结构中,您应该尝试将层次结构保持为树(节点有一个父节点),而不是图。
更多关于钻石的信息(编辑 2017-05-03)
C++ 中的 Diamond of Dread 的真正问题( 假设设计是合理的 - 审查您的代码! ),是 您需要做出选择:
A
是否希望在您的布局中存在两次,这是什么意思?如果是,那么一定要从它继承两次。这种选择是问题所固有的,并且在 C++ 中,与其他语言不同,您实际上可以做到这一点,而无需教条强制您在语言级别进行设计。
但与所有权力一样,这种权力伴随着责任:审查你的设计。
3. 接口
零个或一个具体类的多重继承,以及零个或多个接口通常是可以的,因为您不会遇到上面描述的恐惧钻石。事实上,这就是 Java 中的工作方式。
Usually, what you mean when C inherits from
A
andB
is that users can useC
as if it was anA
, and /或好像它是B
。在 C++ 中,接口是一个抽象类,它具有:
零到一个真实对象的多重继承,以及零个或多个接口不被认为是“臭”(至少,不是那么多)。
有关 C++ 抽象接口的更多信息(编辑 2017-05-03)
首先,NVI 模式可用于生成接口,因为 真正的标准是没有状态(即没有成员变量,除了
this
)。您的抽象接口的重点是发布合同(“您可以这样称呼我,这样称呼我”),仅此而已。只有抽象虚拟方法的限制应该是一种设计选择,而不是一种义务。其次,在 C++ 中,从抽象接口虚拟继承是有意义的(即使有额外的成本/间接)。如果你不这样做,并且接口继承在你的层次结构中出现多次,那么你就会有歧义。
第三,面向对象很棒,但它不是 C++ 中 的 The Only Truth Out There TM 。使用正确的工具,并永远记住您在 C++ 中还有其他范式提供不同类型的解决方案。
4. 你真的需要多重继承吗?
有时候是的。
Usually, your
C
class is inheriting fromA
andB
, andA
andB
are two unrelated objects (即不在同一层次结构中,没有共同点,不同的概念等)。例如,您可以拥有一个系统
Nodes
具有 X、Y、Z 坐标,能够进行大量几何计算(可能是一个点,几何对象的一部分)并且每个节点都是一个自动代理,能够与其他代理进行通信。也许您已经可以访问两个库,每个库都有自己的命名空间(使用命名空间的另一个原因……但您使用命名空间,不是吗?),一个是
geo
另一个是ai
所以你有你自己的
own::Node
派生自ai::Agent
和geo::Point
。这是您应该问自己是否不应该使用构图的时刻。如果
own::Node
真的既是ai::Agent
又是geo::Point
,那么组合就不行了。然后你需要多重继承,让你的
own::Node
根据它们在 3D 空间中的位置与其他代理进行通信。(您会注意到
ai::Agent
和geo::Point
完全、完全、完全不相关……这大大降低了多重继承的危险)其他情况(编辑 2017-05-03)
还有其他情况:
this
与其他部分进行通信时)有时你可以使用组合,有时 MI 更好。关键是:你有选择。负责任地做(并审查您的代码)。
5. 那么,我应该做多重继承吗?
大多数时候,根据我的经验,不会。 MI 不是正确的工具,即使它看起来很有效,因为它可以被懒惰的人用来在没有意识到后果的情况下将功能堆在一起(比如制作
Car
两者都是Engine
和一个Wheel
)。但有时,是的。到那时,没有什么比 MI 更有效了。
但是因为 MI 很臭,准备好在代码审查中捍卫你的架构(捍卫它是一件好事,因为如果你不能捍卫它,那么你不应该这样做)。