我听说过的唯一基本原因是基类的菱形问题。我就是不能接受。对我来说,它的表现非常糟糕,例如:“嗯,有可能将其弄糟,所以这自然是一个坏主意。”您可以用一种编程语言来破坏任何东西,我的意思是任何东西。我只是不能认真对待这一点,至少在没有更彻底的解释的情况下也是如此。
仅意识到这一问题就占了90%。此外,我想我几年前听说过涉及“信封”算法或类似问题的通用解决方法(有人敲响铃吗?)。
关于钻石问题,我能想到的唯一潜在的真正问题是,如果您尝试使用第三方库,并且看不到该库中的两个看似无关的类具有共同的基类,那么除了文档之外,还有一种简单的语言假设该功能可能需要您明确声明要创建菱形的意图,然后才能为您实际编译一颗菱形。有了这样的功能,任何钻石的制造要么是有意的,鲁re的,要么是因为人们没有意识到这一陷阱。
所以所有人都这么说...大多数人讨厌任何多重原因吗?继承,还是仅仅是一堆歇斯底里会导致弊大于利?这里有我没看到的东西吗?谢谢。
示例
汽车扩展了WheeledVehicle,KIASpectra扩展了汽车和电子产品,KIASpectra包含收音机。为什么KIASpectra不包含电子产品?
因为它是电子的。继承与构成应该始终是一种关系,而必须具有一种关系。
因为它是电子关系。那东西上下都有电线,电路板,开关等。
因为它是电子的。如果冬天电池没电了,就好像遇到了所有轮子突然丢失一样的麻烦。
为什么不使用接口?以#3为例。我不想一遍又一遍地写这个,而且我真的不想创建一些奇怪的代理帮助器类来做到这一点:
private void runOrDont()
{
if (this.battery)
{
if (this.battery.working && this.switchedOn)
{
this.run();
return;
}
}
this.dontRun();
}
(我们不在乎该实现的好坏。)您可以想象与电子相关的这些功能中有多少可能不相关
我不确定是否要解决该示例,因为那里有解释的余地。您还可以考虑从平面扩展Vehicle和FlyingObject,以及从Bird扩展Animal和FlyingObject,或者从更纯粹的示例方面进行思考。
#1 楼
在许多情况下,人们使用继承为类提供特征。例如,以飞马为例。通过多重继承,您可能会说飞马扩展了马和鸟,因为您已将鸟归为带翅膀的动物。鸟具有佩加西所没有的其他特征。例如,鸟产卵,Pegasi有活产。如果继承是传递共有特征的唯一方法,那么就没有办法将飞蛋的特征排除在飞马之外。
一些语言选择使特征成为语言中的显式构造。他人会通过从语言中删除MI来轻轻地引导您朝该方向发展。无论哪种方式,我都无法想到“我确实需要MI才能正确完成此操作”的单个案例。
也让我们讨论一下真正的继承是什么。从类继承时,您需要依赖该类,但还必须支持该类支持的契约,包括隐式和显式。
以从a继承的正方形为例。长方形。矩形公开了length和width属性,还公开了getPerimeter和getArea方法。该正方形将覆盖长度和宽度,以便在设置一个时将另一个设置为与getPerimeter匹配,而getArea将起作用(2 * length + 2 * width表示周长,length * width表示面积)。
如果将正方形的这种实现方式替换为矩形,则只有一个测试用例会中断。
var rectangle = new Square();
rectangle.length= 5;
rectangle.width= 6;
Assert.AreEqual(30, rectangle.GetArea());
//Square returns 36 because setting the width clobbers the length
用一个继承链就可以正确解决问题。当您将另一种添加到混合中时,情况变得更糟。
我在MI中用Pegasus提到的陷阱以及Rectangle / Square关系都是无经验的班级设计的结果。基本上,避免多重继承是一种帮助新手开发人员避免开枪的方法。像所有设计原则一样,根据这些原则进行纪律和培训可以使您及时发现何时可以脱离它们。请参阅Dreyfus技能习得模型,在专家级别,您的内在知识超越了对准则/原则的依赖。当规则不适用时,您可以“感到”。
我也同意,我在某种程度上欺骗了为什么MI被皱眉的“现实世界”示例。
让我们看一下UI框架。具体来说,让我们看一些小工具,这些小工具一开始可能看起来像是其他两个的组合。像ComboBox。 ComboBox是具有支持的DropDownList的TextBox。即我可以输入一个值,也可以从预先设定的值列表中进行选择。天真的方法是从TextBox和DropDownList继承ComboBox。
但是您的Textbox会从用户键入的内容中得出其值。当DDL从用户选择的内容中获得价值时。谁先行? DDL可能被设计为验证和拒绝任何不在其原始值列表中的输入。我们会否重覆这种逻辑?这意味着我们必须公开内部逻辑,以便继承者可以覆盖。或更糟糕的是,将逻辑添加到仅用于支持子类的基类中(违反了依赖倒置原则)。
避免MI可以帮助您完全避开此陷阱。并可能导致您提取UI小部件的常见,可重用特征,以便可以根据需要应用它们。一个很好的例子就是WPF附加属性,它允许WPF中的框架元素提供一个属性,另一个框架元素可以使用该属性而无需继承父框架元素。
例如,网格是WPF中的布局面板,它具有附加的“列”和“行”属性,这些属性指定在网格的布局中应放置子元素的位置。没有附加属性,如果我想在Grid中安排一个Button,则Button必须从Grid派生,以便它可以访问Column和Row属性。
开发人员进一步采用了此概念,并使用了附加属性,作为组件行为的一种方式(例如,这是我在WPF包含DataGrid之前编写的使用附加属性制作可排序GridView的文章)。该方法已被认为是一种称为“附加行为”的XAML设计模式。
希望此方法提供了更多有关为何通常不赞成多重继承的见解。
评论
“老兄,我真的需要MI才能正确地做到这一点”-请参阅我的回答。简而言之,满足is-a关系的正交概念对MI有意义。它们非常罕见,但是存在。
–MrFox
13年11月14日在19:38
直到几个小时前,我才意识到特质和混合蛋白(我必须花一些时间去仔细阅读它们),所以您的回答是真正有意义的。实际上,它们似乎是一个很好的代理。谢谢。
–装甲危机
2013年11月14日20:35
老兄,我讨厌那个正方形的例子。还有很多不需要可变性的示例。
– asmeurer
13年11月15日,0:09
我喜欢“提供一个真实的例子”的答案是讨论一个虚构的生物。极好的讽刺+1
– jjathman
13年11月15日在2:04
我不认为这些示例真的有用...没有人说飞马座是鸟和马...你说它是WingedAnimal和HoofedAnimal。正方形和矩形的示例更具意义,因为您发现自己的对象的行为与根据其定义的定义所想的不同。但是,我认为这种情况将是程序员的错,因为他们没有抓住这个问题(如果确实确实是一个问题)。
–理查德
13年11月16日下午5:01
#2 楼
我这里没有看到什么吗?
允许多重继承使有关函数重载和虚拟分派的规则以及对象布局周围的语言实现更加棘手。这些对语言设计者/实施者产生了很大的影响,并提高了本已很高的门槛,以使一种语言得以完成,稳定和被采用。
我看到(有时也提出过)另一个常见的论点是,通过拥有两个以上的基类,您的对象几乎总是违反单一责任原则。两个或两个以上的基类都是很好的自包含类,它们都有自己的责任(导致违规),或者它们是部分/抽象类型,它们相互协作以承担单个内聚的责任。
在其他情况下,您有3种情况:
A对B一无所知-太好了,您可以合并这些类,因为您很幸运。
A对B有所了解-为什么不知道t A只是继承自B?
A和B彼此了解-您为什么不参加一个班?个人认为,多重继承有一个不好的说唱力,而完善的特征样式组成系统真的很强大/有用。 。但是有很多方法可能无法很好地实现它,并且有很多原因使它在C ++之类的语言中不是一个好主意。
[edit]关于您的示例,这很荒谬。起亚有电子产品。它有一个引擎。同样,它的电子设备具有电源,恰好是汽车电池。继承,更不用说多重继承了。
评论
另一个有趣的问题是基类及其基类如何初始化。封装>继承。
–丹尼尔·格拉茨(Daniel Gratzer)
13年11月14日在16:41
“一个完善的特征样式组合系统将非常强大/有用...”-这些被称为mixins,它们非常强大/有用。可以使用MI实现Mixins,但是mixins不需要多重继承。有些语言固有地支持mixin,而没有MI。
– BlueRaja-Danny Pflughoeft
13年11月14日在20:21
特别是对于Java,我们已经有多个接口和INVOKEINTERFACE,因此MI不会对性能产生任何明显的影响。
–丹尼尔·卢巴罗夫(Daniel Lubarov)
13年15月15日在5:10
Tetastyn,我同意这个例子很糟糕,没有解决他的问题。继承不是针对形容词(电子)的,而是名词(车辆)的。
–理查德
13年11月16日下午5:05
#3 楼
禁止使用该工具的唯一原因是因为它使人们可以轻松地用脚射击自己。在这种讨论中,通常接下来要讨论的是是否具有工具的灵活性比不踩脚的安全性更重要。该论点没有绝对正确的答案,因为与编程中的大多数其他事物一样,答案取决于上下文。
如果您的开发人员对MI感到满意,并且MI在您所了解的上下文中有意义正在这样做,那么您将以不支持它的语言非常怀念它。同时,如果团队对它不满意,或者没有真正的需求,而人们只是“因为他们有能力”使用它,那将适得其反。
但是,没有一个令人信服的绝对真实的论据证明多重继承是一个坏主意。
EDIT
这个问题的答案似乎是一致的。为了成为魔鬼的拥护者,我将提供一个多重继承的很好的例子,如果不这样做会导致黑客入侵。
假设您正在设计一个资本市场应用程序。您需要证券数据模型。有些证券是股票产品(股票,房地产投资信托等),有些是债券(债券,公司债券),有些是衍生产品(期权,期货)。
因此,如果您避免MI,您将获得非常清晰,简单的继承树。股票将继承股权,债券将继承债务。到目前为止还不错,但是衍生产品呢?它们可以基于类似股票的产品还是类似借方的产品?好的,我想我们将使继承树更多。请记住,某些衍生产品基于股票产品,债务产品或两者都不基于。因此我们的继承树变得越来越复杂。
然后是业务分析师,告诉您现在我们支持指数证券(指数期权,指数期货期权)。这些东西可以基于权益,债务或衍生工具。这越来越混乱了!我的指数期货期权会衍生股本->股票->期权->指数吗?为什么不选择股票->股票->指数->期权?如果有一天我能在我的代码中找到两者(发生;这是真实的故事)怎么办?
这里的问题是,这些基本类型可以混合在任何不能自然地从另一个中派生的置换中。对象是通过关系定义的,因此合成毫无意义。多重继承(或mixin的类似概念)是这里唯一的逻辑表示。
解决此问题的真正方法是使用多重继承定义和混合Equity,Debt,Derivative,Index类型。创建您的数据模型。这将创建既有意义又易于代码重用的对象。
评论
我曾在金融业工作。在财务软件中,功能编程优于O-O是有原因的。您已经指出了。 MI无法解决此问题,反而加剧了这一问题。相信我,我无法告诉您,与金融客户打交道时,我听到过多少次“这...例外”,这成为我生存的祸根。
–迈克尔·布朗(Michael Brown)
13年11月14日在20:02
对于我来说,这似乎可以解决……实际上,它似乎比其他任何一种都更适合接口。股票和债务均实现ISecurity。派生具有ISecurity属性。如果合适的话,它本身可能就是一个ISecurity(我不知道财务状况)。 IndexedSecurities再次包含一个接口属性,该属性将应用于允许其基于的类型。如果它们都是ISecurity,那么它们都具有ISecurity属性,并且可以任意嵌套...
–鲍勃森
13年11月14日在20:30
@Bobson的问题在于,您必须为每个实现者重新实现接口,并且在许多情况下,接口是相同的。您可以使用委派/组成,但随后您将失去对私人成员的访问权限。而且由于Java没有委托/ lambda,所以实际上没有好的方法。除非可能使用如Aspect4J的Aspect工具
–迈克尔·布朗(Michael Brown)
13年11月14日在20:45
@鲍勃森正是麦克·布朗所说的。是的,您可以设计带有接口的解决方案,但这将很麻烦。但是,您使用接口的直觉非常正确,这是对mixins /多重继承的隐秘需求:)。
–MrFox
13年11月14日在20:56
这碰到了一种奇怪的情况,即面向对象的程序员倾向于在继承方面进行思考,而常常不能接受委托作为一种有效的面向对象的方法,这种方法通常会产生一种更精简,更可维护和可扩展的解决方案。在金融和医疗保健部门工作过,我已经看到MI可能造成难以控制的混乱,尤其是当税收和健康法律逐年变化并且LAST年份的对象的定义对于本年度无效(但仍必须执行任一年度的功能) ,并且必须可以互换)。合成产生的代码更精简,更易于测试,并且随着时间的推移成本更低。
–肖恩·威尔逊(Shaun Wilson)
13年15月15日在9:53
#4 楼
这里的其他答案似乎主要是理论上的。因此,这是一个简化的具体Python示例,实际上我已经彻底陷入其中,需要大量重构: class Foo(object):
def zeta(self):
print "foozeta"
class Bar(object):
def zeta(self):
print "barzeta"
def barstuff(self):
print "barstuff"
self.zeta()
class Bang(Foo, Bar):
def stuff(self):
self.zeta()
print "---"
self.barstuff()
z = Bang()
z.stuff()
编写
Bar
时假设它具有自己的zeta()
实现,这通常是一个很好的假设。子类应该适当地重写它,以便正确执行操作。不幸的是,它们的名称恰好是相同的-所做的事情却大不相同,但是Bar
现在调用了Foo
的实现:foozeta
---
barstuff
foozeta
没有引发任何错误,该应用程序开始执行操作时只是出现了一点点错误,并且导致它的代码更改(创建
Bar.zeta
)似乎并不是问题所在。评论
通过调用super()如何避免Diamond问题?
–装甲危机
13年11月14日在20:16
@Panzercrisis对不起,没关系。我记错了“钻石问题”通常是指哪个问题-Python有一个单独的问题,该问题是由super()绕过的菱形继承引起的
–伊兹卡塔
2013年11月14日20:19
我很高兴C ++的MI设计得更好。以上错误根本不可能。您必须手动消除歧义,然后才能编译代码。
–托马斯·埃丁
13年11月17日下午4:16
Foo将Bang中的继承顺序更改为Bar,Foo也可以解决此问题-但您的观点很不错,+ 1。
– Sean Vieira
2013年11月19日下午3:34
@ThomasEding您仍然可以手动消除歧义:Bar.zeta(self)。
–泰勒·克朗普顿(Tyler Crompton)
2014年8月7日在21:43
#5 楼
我认为使用正确语言的MI并没有任何真正的问题。关键是允许菱形结构,但要求子类型提供其自己的覆盖,而不是编译器根据某些规则选择一种实现。我用番石榴(Guava)来做这件事正在努力。 Guava的一个功能是我们可以调用方法的特定超类型的实现。因此,很容易指出应该“继承”哪个超类型实现,而无需任何特殊语法:
type Sequence[+A] {
String toString() {
return "[" + ... + "]";
}
}
type Set[+A] {
String toString() {
return "{" + ... + "}";
}
}
type OrderedSet[+A] extends Sequence[A], Set[A] {
String toString() {
// This is Guava's syntax for statically invoking instance methods
return Set.toString(this);
}
}
如果我们不给
OrderedSet
自己的toString
,我们将得到编译错误。毫不奇怪。我发现MI对集合特别有用。例如,我喜欢使用
RandomlyEnumerableSequence
类型来避免为数组,双端队列等声明getEnumerator
:type Enumerable[+A] {
Source[A] getEnumerator();
}
type Sequence[+A] extends Enumerable[A] {
A get(Int index);
}
type RandomlyEnumerableSequence[+A] extends Sequence[A] {
Source[A] getEnumerator() {
...
}
}
type DynamicArray[A] extends MutableStack[A],
RandomlyEnumerableSequence[A] {
// No need to define getEnumerator.
}
如果我们没有MI,我们可以编写一个供多个集合使用的
RandomAccessEnumerator
,但是必须写一个简短的getEnumerator
方法仍然会增加样板。类似地,MI对于继承集合的
equals
,hashCode
和toString
的标准实现很有用。 >评论
@AndyzSmith,我明白您的意思,但并非所有继承冲突都是实际的语义问题。考虑我的示例-没有一种类型对toString的行为做出承诺,因此重写其行为不会违反替换原则。有时,在行为相同但算法不同(尤其是集合)的方法之间,有时也会出现“冲突”。
–丹尼尔·卢巴罗夫(Daniel Lubarov)
13年11月17日在7:37
@Asmageddon同意,我的示例仅涉及功能。您认为mixins的优点是什么?至少在Scala中,特征本质上只是允许MI的抽象类-它们仍然可以用于标识并且仍然带来钻石问题。还有其他语言的mixin具有更有趣的区别吗?
–丹尼尔·卢巴罗夫(Daniel Lubarov)
2014年10月5日在21:30
从技术上讲,抽象类可以用作接口,对我来说,好处仅仅是结构本身就是一个“使用技巧”,这意味着我知道看到代码后会发生什么。就是说,我目前在Lua中进行编码,而Lua没有固有的OOP模型-现有的类系统通常允许将表包括为mixin,而我正在编码的系统则使用类作为mixin。唯一的区别是,您只能对身份使用(单个)继承,对于功能(没有身份信息)使用混合,并且可以进行进一步检查的接口。也许这还不是更好的方法,但是对我来说很有意义。
–拉马吉登
2014年10月6日20:01
为什么将有序扩展集和序列称为菱形?这只是一个联接。它缺少帽子顶点。你为什么称它为钻石?我在这里问,但这似乎是一个禁忌问题。您怎么知道您需要将此三角结构称为“钻石”而不是“加入”?
–Val
15年7月14日在18:25
@RecognizeEvilasWaste该语言具有一个隐式的Top类型,该类型声明了toString,因此实际上有一个菱形结构。但是我认为您有一个要点-没有菱形结构的“连接”会产生类似的问题,并且大多数语言都以相同的方式处理这两种情况。
–丹尼尔·卢巴罗夫(Daniel Lubarov)
15年7月14日在21:20
#6 楼
继承(无论是多重继承还是其他继承)并不那么重要。如果两个不同类型的对象是可替换的,那么即使没有通过继承链接它们也很重要。链接列表和字符串的共同点很少,不需要通过继承链接。 ,但是如果我可以使用
length
函数来获取任一元素中的元素数量,则很有用。继承是避免重复执行代码的一种技巧。如果继承可以节省您的工作,并且多重继承比单继承可以节省更多的工作,那么这就是所有需要的理由。
我怀疑某些语言不能很好地实现多重继承,并且对于那些语言的从业者来说,这就是多重继承的意义。提到C ++程序员的多重继承,我想到的是一个类通过两个不同的继承路径最终获得两个基类的副本时是否出现问题,以及是否在基类上使用
virtual
,以及关于析构函数的困惑,在许多语言中,类的继承与符号的继承混为一谈。当从类B派生类D时,不仅要创建类型关系,而且由于这些类还充当词法命名空间,因此,除了将符号从B名称空间导入D名称空间外,还要处理符号输入。 B和D本身发生的情况的语义。因此,多重继承带来了符号冲突的问题。如果我们从
card_deck
和graphic
继承,它们都“拥有” draw
方法,那么对产生的对象draw
意味着什么?没有此问题的对象系统是Common Lisp中的对象系统。 Lisp程序中可能使用了多种继承,这可能并非偶然。实施不佳,不便之处(例如多重继承)应该受到仇恨。
评论
您的带有list和string容器的length()方法的示例是不好的,因为这两个做的事情完全不同。继承的目的不是减少代码重复。如果要减少代码重复,可以将公共部分重构为一个新类。
–BЈовић
13年11月15日在7:07
@BЈовић两者根本没有“完全”完成不同的事情。
–卡兹
13年11月15日在7:09
比较字符串和列表,可以看到很多重复。使用继承来实现这些通用方法将是完全错误的。
–BЈовић
13年15月15日在7:17
@BЈовић答案中没有说字符串和列表应该通过继承链接;恰恰相反。可以在长度操作中替换列表或字符串的行为与继承的概念无关(在这种情况下没有帮助:我们不太可能通过尝试共享实现来实现任何事情)长度函数的两种方法之间)。尽管如此,还是可能会有一些抽象的继承:列表和字符串都是序列类型的(但是不提供任何实现)。
–卡兹
13年15月15日在8:43
同样,继承是将部件重构为通用类(即基类)的一种方式。
–卡兹
2014年8月5日在18:51
#7 楼
据我所知,部分问题(除了使您的设计更难以理解(尽管更易于编写代码))是编译器将为类数据节省足够的空间,从而使在以下情况下会浪费内存:(我的示例可能不是最好的,但是出于相同的目的,尝试获取有关多个内存空间的要点,这是我想到的第一件事: P)
考虑当班犬从犬类和宠物中伸出的DDD时,犬类具有一个变量,该变量指示应以DietKg为名食用的食物量(整数),但宠物也可以为此,通常使用相同的名称来具有另一个变量(除非您设置另一个变量名称,否则您将编写额外的代码来处理和保持bouth变量的完整性,这是您想避免的最初问题),然后您将有两个用于完全相同目的的内存空间,为避免这种情况,您将必须修改compi可以在相同的名称空间下识别该名称,并只为该数据分配一个内存空间,不幸的是,在编译时必须确定该空间。
您当然可以设计一个languaje来指定这样的变量可能已经在其他地方定义了空间,但是最后程序员应该指定该变量所引用的内存空间在哪里(还有额外的代码)。
请相信实现的人们这个想法真的很难,但是我很高兴你问,你的前提是改变范式的那个;),并考虑到这一点,我并不是说这是不可能的(但是许多假设和一个多阶段编译器必须实施,而且非常复杂),我只是说它还不存在,如果您为自己的编译器启动一个能够执行“ this”(多重继承)的项目,请告诉我,我很高兴加入您的团队。
评论
在什么时候编译器可以假设来自不同父类的同名变量可以安全组合?您有什么保证caninus.diet和pet.diet实际上可以达到相同的目的?您将如何处理具有相同名称的函数/方法?
– Mindor先生
13年11月14日在23:03
哈哈哈我完全同意你的观点@Mindor先生,这正是我在回答中所说的,我想到的唯一解决办法就是面向原型的编程,但是我并不是说这是不可能的,这正是我说在编译时必须进行许多假设,并且程序员必须编写一些额外的“数据” /规范的原因(尽管这是更多的编码,这是原始问题)
– Ordiel
13年15月15日在14:59
#8 楼
相当长一段时间以来,我从未真正想到某些编程任务与其他编程任务有多么完全不同,如果所使用的语言和模式是针对问题空间量身定制的,这对您有多大帮助。是单独工作还是与您编写的代码基本隔离,这是一个完全不同的问题空间,它是从印度的40个人那里继承了一个代码库而来的,这个人花了一年的时间才将代码库交给您,而没有任何过渡帮助。
想象一下,您刚刚被梦想中的公司雇用,然后继承了这样的代码库。进一步假设,顾问们已经学习了继承和多重继承(并因此而对其产生了浓厚的兴趣)……您能想象一下您将要从事的工作吗?
当您继承代码时,最重要的功能是这是可以理解的,并且各个部分是隔离的,因此可以独立进行处理。当然,当您第一次编写诸如多重继承之类的代码结构时,可能会节省一些重复时间,并且似乎符合您当时的逻辑心情,但是下一个人有更多东西需要解决。您的代码中的代码也使独立理解和修改代码变得更加困难,而多重继承则使它加倍。
当您作为团队的一员工作时,您希望以最简单的代码作为目标,从而为您提供绝对的没有多余的逻辑(这就是DRY的真正意思,不是说您不必键入太多就不必在两个地方更改代码来解决问题!)
有更简单的方法可以实现DRY代码比多重继承更重要,因此将其包含在一种语言中只会使您面临其他人可能与您的理解水平不高的问题。如果您的语言无法为您提供使代码保持DRY的简单/不太复杂的方法,这甚至很诱人。
评论
进一步想像一下,顾问已经学习了-我见过很多非MI语言(例如.net),这些顾问对基于接口的DI和IoC感到疯狂,使整个事情变得非常混乱。 MI不会使情况变得更糟,可能会使情况变得更好。
– gbjbaanb
15年12月3日,11:23
@gbjbaanb是的,对于没有被烧毁的人来说,将任何功能弄乱很容易-实际上,学习将其弄乱以了解如何最佳使用它的功能几乎是一项要求。我有点好奇,因为您似乎在说没有MI会增加更多我从未见过的DI / IoC,我不认为您拥有所有糟糕的接口/ DI的顾问代码都应该是最好还有一整个疯狂的多对多继承树,但这可能是我对MI的经验不足。我是否可以阅读有关用MI替换DI / IoC的页面?
– Bill K
2015年12月3日,17:32
#9 楼
反对多重继承的最大论点是,在严格限制它(*)的框架中,可以提供一些有用的能力,并且可以保留一些有用的公理,但没有这样的限制就不能提供和/或保持。其中包括:具有多个单独编译的模块的功能包括从其他模块的类继承的类,并重新编译包含基类的模块而不必重新编译从其继承的每个模块。该基类。一种类型可以从父类继承成员实现而无需重新实现派生类型的能力
任何对象实例都可以直接向上或向下转换到其自身的公理或其任何基本类型,并且这种上下转换(以任何组合或顺序)始终保持身份
的公理,即派生类重写并链接到基类成员时,基类将是
(*)通常,通过要求将支持多重继承的类型声明为“接口”而不是类,并且不允许接口进行操作,通常不会通过链接代码进行调用。普通班可以做的一切。
如果希望允许广义多重继承,则必须让位。如果X和Y都继承自B,则它们都覆盖同一个成员M并链接到基本实现,并且如果D继承自X和Y但不覆盖M,则给定实例q的类型D,应该是什么(( B)q).M()吗?禁止进行这种强制转换会违反说任何对象都可以转换为任何基本类型的公理,但是强制转换和成员调用的任何可能行为都会违反关于方法链接的公理。可以要求仅将类与针对它们编译的基类的特定版本组合加载,但这通常很尴尬。让运行时拒绝加载可以通过一条以上路径到达任何祖先的任何类型的方法可能是可行的,但会大大限制多重继承的用处。仅当不存在冲突时才允许共享继承路径会导致以下情况:旧版本的X与旧的或新的Y兼容,旧的Y与旧的或新的X兼容,但新的X和新的Y将兼容兼容,即使它们本身并没有任何重大改变。
某些语言和框架确实允许多重继承,因为从MI中获得的收益更重要超过必须放弃的范围。但是,MI的成本是巨大的,并且在许多情况下,接口以很小的一部分成本即可提供90%的MI收益。
评论
不赞成投票的人会发表评论吗?我相信我的答案提供了其他语言无法提供的信息,这些信息涉及可以通过禁止多重继承获得的语义优势。允许多重继承将需要放弃其他有用的事实,这将是一个比多重继承更看重其他事物的人反对MI的充分理由。
–超级猫
2014年8月3日在18:54
使用与单继承相同的技术,可以轻松解决或完全避免MI问题。除了第二个,您所提到的所有能力/原则都不是MI固有的阻碍因素...并且,如果您要继承两个为同一方法提供不同行为的类,则无论如何要解决这种歧义。但是,如果您发现自己必须这样做,则可能已经在滥用继承。
– cHao
15年5月26日在15:07
@cHao:如果要求派生类必须重写父方法并且不链接到父方法(与接口相同的规则),则可以避免这些问题,但这是一个非常严格的限制。如果使用一种模式,其中FirstDerivedFoo的Bar替代行为只不过链到了受保护的非虚拟FirstDerivedFoo_Bar,而SecondDerivedFoo的替代链则保护了非虚拟SecondDerivedBar,并且如果要访问基本方法的代码使用了这些受保护的非虚拟方法,这可能是一个可行的方法...
–超级猫
15年5月26日在15:47
...(避开语法base.VirtualMethod),但是我不知道任何语言支持会特别促进这种方法。我希望有一种干净的方法可以将一个代码块附加到非虚拟受保护方法和具有相同签名的虚拟方法上,而无需“单线”虚拟方法(最终需要花费多于一行代码)在源代码中)。
–超级猫
15年5月26日在15:52
MI没有内在的要求,即类不能调用基本方法,也不需要特定的理由。考虑到该问题也会影响SI,因此怪罪MI似乎有点奇怪。
– cHao
15年5月26日在17:56
评论
它也鼓励对合成的继承...(当应该相反时)“您可以将其拧紧”是删除功能的完全正确的理由。在这一点上,现代语言设计有些混乱,但是您通常可以将语言分为“力量来自约束”和“力量来自灵活性”。我认为这两者都不是“正确的”,它们都有自己的优点。 MI是经常用于Evil的事物之一,因此限制性语言将其删除。灵活的人则不会,因为“更多时候”并不是“字面上总是”。也就是说,我认为对于一般情况,mixin / traits是更好的解决方案。
除了接口以外,还有多种语言中的多种继承的更安全替代方案。查看Scala的特质-它们的作用类似于具有可选实现的接口,但是有一些限制可以防止出现钻石问题。
另请参阅此先前封闭的问题:programmers.stackexchange.com/questions/122480/…
KiaSpectra不是电子产品;它有电子产品,可能是电子汽车(将扩展汽车...)