Scala的特征如何避免“钻石错误”?


16

(注意:出于明显的原因,我在标题中使用了“错误”而不是“问题”。))。

我对Scala的Traits做了一些基础阅读。它们类似于Java或C#中的接口,但是它们确实允许方法的默认实现。

我在想:这难道不会引起“钻石问题”吗?这就是为什么许多语言首先避免多重继承的原因?

如果是这样,Scala如何处理呢?


分享您的研究成果对所有人都有帮助。告诉我们您尝试过的内容以及为什么它不能满足您的需求。这表明您已花时间尝试自我帮助,这使我们免于重复显而易见的答案,并且最重要的是,它可以帮助您获得更具体和相关的答案。另请参阅“ 如何提问
2014年

2
@gnat:这是一个概念性问题,而不是一个具体的问题。如果他问“我在Scala上这堂课,并且给我我认为可能与钻石问题有关的问题,我该如何解决?” 那么您的评论将是适当的,但随后的问题将属于SO。:P
Mason Wheeler 2014年

@MasonWheeler我也对Scala做了一些基础阅读。在我读过的书中对“钻石”的首次搜索给了我答案:“特征具有Java接口构造的所有功能。但是特征可以在其上实现方法。如果您熟悉Ruby,则特征相似对于Ruby的mixins。您可以将多个特征混合到一个类中。特性不能接受构造函数参数,但它们的行为类似于类。这使您能够进行一些处理,而不会出现钻石问题。在这个问题上缺乏努力感到很公然
gna 2014年

7
阅读该声明并不能告诉您如何做。
Michael Brown

Answers:


22

钻石问题是无法决定选择哪种方法的实现。Scala通过定义选择哪种实现作为语言规范的一部分来解决此问题(请参阅Wikipedia文章中有关Scala的部分)。

当然,相同的顺序定义也可以在类多重继承中使用,那么为什么还要打扰特性呢?

IMO之所以是构造函数。构造函数有一些常规方法没有的限制-每个对象只能调用一次,必须为每个新对象调用它们,并且子类的构造函数必须在第一条指令中调用其父类的构造函数(大多数语言会如果您不需要传递参数,则可以为您隐式执行此操作)。

如果B和C继承A,D继承B和C,并且B和C的构造函数都调用A的构造函数,则D的构造函数将两次调用A的构造函数。定义选择哪个实现像斯卡拉做了与方法不会在这里工作,因为这两个 B的和C的构造函数必须被调用。

特质避免了这个问题,因为它们没有构造函数。


1
可以使用C3线性化仅一次调用构造函数-这就是Python进行多重继承的方式。在我的头顶上,D <B | C <菱形的线性化是D-> B-> C->A。此外,谷歌搜索显示我Scala特征可以具有可变变量,因此肯定有一个构造函数在那里?但是,如果它在
后台

...特质似乎是表达所有将接口继承和组合+委派相结合的样板的非常简洁的方法,这是重用行为的正确方法。
2014年

@Doval我在多重继承的Python中构造函数的经验是,它们是一种皇家痛苦。每个构造函数都不知道它将以什么顺序调用,因此也不知道其父构造函数的签名是什么。通常的解决方案是让每个构造函数使用一堆关键字参数,并将未使用的参数传递给其超级构造函数,但是如果您需要使用不遵循该约定的现有类,则无法安全地继承它。
James_pic

另一个问题是,为什么C ++对于钻石问题没有选择理智的政策?
用户

20

Scala通过称为“特征线性化”的方法避免了钻石问题。基本上,它从右到左扩展特征中查找方法的实现。简单的例子:

trait Base {
   def op: String
}

trait Foo extends Base {
   override def op = "foo"
}

trait Bar extends Base {
   override def op = "bar"
}

class A extends Foo with Bar
class B extends Bar with Foo

(new A).op
// res0: String = bar

(new B).op
// res1: String = foo

话虽如此,它查找的特征列表可能包含比您明确给出的特征更多的特征,因为它们可能扩展了其他特征。此处给出详细说明: 作为可堆叠修改的特性 和此处线性化的更完整示例: 为什么不多重继承?

我相信在其他编程语言中,这种行为有时也称为“方法解析顺序”或“ MRO”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.