使用多重继承是一个好主意还是我可以做其他事情?
多重继承(缩写为MI)闻起来,这意味着通常,它是出于不好的原因而完成的,它会在维护者面前反击.
考虑功能的组合,而不是继承
警惕恐惧钻石
考虑继承多个接口而不是对象
有时,多重继承是正确的.如果是,则使用它.
准备好在代码审查中捍卫您的多重继承架构
这对于继承来说是正确的,因此,对于多重继承来说更是如此.
你的对象真的需要从另一个继承吗?A Car
不需要继承Engine
工作,也不需要继承Wheel
.A Car
有Engine
四个Wheel
.
如果你使用多重继承来解决这些问题而不是组合,那么你做错了.
通常情况下,你有一个类A
,然后B
和C
来自继承A
.而且(不要问我为什么)有人然后决定D
必须继承B
和C
.
我在8个8年里遇到过这种问题两次,看到因为:
多少钱一个错误是从一开始(在这两种情况下,D
不应该从两个继承B
和C
),因为这是不好的架构(其实,C
不应该存在在所有...)
有多少维护者为此付出了代价,因为在C++中,父类A
在其孙子类中出现了两次D
,因此,更新一个父字段A::field
意味着更新它两次(通过B::field
和C::field
),或者有一些东西是默认错误并且崩溃,之后(新的指针B::field
,并删除C::field
...)
如果这不是你想要的,那么在C++中使用关键字virtual来限定继承可以避免上面描述的双重布局,但无论如何,根据我的经验,你可能做错了......
在对象层次结构中,您应该尝试将层次结构保持为树(节点具有一个父节点),而不是图形.
C++中的恐惧钻石的真正问题(假设设计合理 - 让你的代码得到审查!),你需要做出选择:
这个类是否适合A
在你的布局中存在两次,这是什么意思?如果是,那么一定要从它继承两次.
如果它只存在一次,那么实际上从它继承.
这个选择是问题所固有的,而且在C++中,与其他语言不同,您实际上可以在没有教条的情况下执行此操作,从而在语言级别强制设计.
但是像所有权力一样,拥有这种权力的责任就在于:让您的设计得到审查
零或一个具体类的多重继承,以及零个或多个接口通常是好的,因为您不会遇到上述的恐惧钻石.事实上,这就是用Java完成的事情.
通常情况下,你的意思是当C从继承A
和B
是用户可以使用C
,如果它是一个A
,和/或如果它是一个B
.
在C++中,接口是一个抽象类,它具有:
其所有方法声明为纯虚拟(后缀为= 0) (删除了2017-05-03)
没有成员变量
零到一个真实对象以及零个或多个接口的多重继承不被认为是"臭"(至少不是那么多).
首先,NVI模式可用于生成接口,因为真正的标准是没有状态(即没有成员变量,除了this
).你的抽象界面的意思是发布合同("你可以用这种方式打电话给我,这样"),仅此而已.仅具有抽象虚拟方法的限制应该是设计选择,而不是义务.
其次,在C++中,从抽象接口虚拟继承是有意义的(即使有额外的成本/间接).如果不这样做,并且接口继承在层次结构中出现多次,那么您将有歧义.
三,面向对象是伟大的,但它不是唯一的真理在那里TM在C++中.使用正确的工具,并始终记住您在C++中提供其他范例,提供不同类型的解决方案.
有时候是.
通常情况下,你的C
类继承A
和B
,和A
和B
是两个不相关的对象(即不在同一层次,没有任何共同之处,不同的概念,等等).
例如,您可以拥有一个Nodes
具有X,Y,Z坐标的系统,能够进行大量的几何计算(可能是一个点,几何对象的一部分),每个节点都是一个自动代理,能够与其他代理进行通信.
也许您已经可以访问两个库,每个库都有自己的命名空间(使用命名空间的另一个原因......但是你使用命名空间,不是吗?),一个正在存在geo
,另一个存在ai
所以你有自己的own::Node
衍生出来ai::Agent
和geo::Point
.
这是你应该问自己是否不应该使用构图的那一刻.如果own::Node
真的真的是a ai::Agent
和a geo::Point
,那么组合就不行了.
然后,您将需要多重继承,own::Node
根据他们在3D空间中的位置与其他代理进行通信.
(你会注意到ai::Agent
并且geo::Point
完全,完全,完全不相关......这大大降低了多重继承的危险)
还有其他情况:
使用(希望是私有的)继承作为实现细节
一些像策略这样的C++习语可以使用多重继承(当每个部分需要通过其他部分进行通信时this
)
来自std :: exception的虚拟继承(异常是必需的虚拟继承吗?)
等等
有时你可以使用成分,有时MI更好.关键是:你有一个选择.负责任地做(并检查您的代码).
大部分时间,根据我的经验,没有.MI是不正确的工具,即使它似乎工作,因为它可以由惰性用来堆特征组合在一起而没有意识到后果(如制作Car
既有Engine
和Wheel
).
但有时候,是的.而在那个时候,没有比MI更好的了.
但是因为MI很臭,所以要准备好在代码审查中捍卫你的架构(并且保护它是一件好事,因为如果你无法保护它,那么你就不应该这样做).
从对Bjarne Stroustrup的采访中:
人们非常正确地说你不需要多重继承,因为你可以用多重继承做任何事情,你也可以用单继承做.你只需使用我提到的委托技巧.此外,您根本不需要任何继承,因为您使用单继承执行的任何操作也可以通过转发类来进行继承.实际上,您也不需要任何类,因为您可以使用指针和数据结构来完成所有操作.但是你为什么要那样做呢?什么时候使用语言设施方便?你什么时候想要一个解决方法?我已经看到了多继承很有用的情况,我甚至看到过很复杂的多继承很有用的情况.通常,我更喜欢使用该语言提供的功能来进行变通
没有理由避免它,它在情况下非常有用.但您需要了解潜在的问题.
最大的一个是死亡钻石:
class GrandParent; class Parent1 : public GrandParent; class Parent2 : public GrandParent; class Child : public Parent1, public Parent2;
您现在在Child中有两个GrandParent"副本".
C++已经想到了这一点,并允许你做虚拟继承来解决问题.
class GrandParent; class Parent1 : public virtual GrandParent; class Parent2 : public virtual GrandParent; class Child : public Parent1, public Parent2;
始终检查您的设计,确保您没有使用继承来节省数据重用.如果你可以用组合表示相同的东西(通常你可以),这是一个更好的方法.
请参阅w:多重继承.
多重继承受到了批评,因此没有用多种语言实现.批评包括:
复杂性增加
语义歧义通常归结为钻石问题.
无法从单个类中显式继承多次
继承顺序改变类语义.
使用C++/Java样式构造函数的语言中的多重继承会加剧构造函数和构造函数链接的继承问题,从而在这些语言中创建维护和可扩展性问题.在构造函数链接范例下,很难实现具有极大变化的构造方法的继承关系中的对象.
现代的解决方法是使用COM和Java接口等接口(纯抽象类).
我可以用其他东西代替这个吗?
是的你可以.我要从GoF偷走.
编程到接口,而不是实现
更喜欢继承的组合
公共继承是一种IS-A关系,有时一个类将是几个不同类的类型,有时反映这一点很重要.
"Mixins"有时也很有用.它们通常是小类,通常不从任何东西继承,提供有用的功能.
只要继承层次结构相当浅(因为它应该几乎总是如此),并且管理得当,您就不可能获得可怕的钻石继承.钻石对于使用多重继承的所有语言来说都不是问题,但是C++对它的处理经常是尴尬的,有时令人费解.
虽然我遇到了多重继承非常方便的情况,但它们实际上相当罕见.这可能是因为当我不需要多重继承时,我更喜欢使用其他设计方法.我更愿意避免混淆语言结构,并且很容易构建继承案例,你必须非常好地阅读手册以弄清楚发生了什么.
你不应该"避免"多重继承,但你应该意识到可能出现的问题,如"钻石问题"(http://en.wikipedia.org/wiki/Diamond_problem),并小心对待给你的力量,你应该拥有所有权力.