所以,我试图建立一个finagle服务器,与哨兵交谈(不重要),偶然发现一个案例,我需要在同一个案例中继承两个类(不是特征)时间,让我们称他们为class SentryHandler extends Handler
和class TwitterHandler extends Handler
,并假设我需要创建MyHandler
,这两个都是继承的。
经过一段时间的愚蠢之后,当我认为如果不使用可怕的“委托模式”是不可能的,我找到了一个解决方案:
trait SentryTrait extends SentryHandler
class MyHandler extends TwitterHandler with SentryTrait
现在,这让我想到:拥有“特质”概念的目的是什么?如果想要强制你可以继承多个特征但只有一个类,那么它似乎非常容易。有点听起来像class
应该是继承的“主要”行(你“扩展一个带有特征的类”,但这不是也可以:你可以extend
一个特征(或没有)一堆其他特征,而且根本没有阶级。
您无法实例化特征,但同样适用于抽象类......
我能想到的唯一真正的区别是特征不能有构造函数参数。但那有什么意义呢? 我的意思是,为什么不呢?这样的问题会怎样?
class Foo(bar: String, baz: String) extends Bar(bar) with Baz(baz)
答案 0 :(得分:4)
您的解决方案(如果我理解正确的话) - 不起作用。你不能在scala中使用多个继承类:
scala> class Handler
defined class Handler
scala> class SentryHandler extends Handler
defined class SentryHandler
scala> class TwitterHandler extends Handler
defined class TwitterHandler
scala> trait SentryTrait extends SentryHandler
defined trait SentryTrait
scala> class MyHandler extends TwitterHandler with SentryTrait
<console>:11: error: illegal inheritance; superclass TwitterHandler
is not a subclass of the superclass SentryHandler
of the mixin trait SentryTrait
class MyHandler extends TwitterHandler with SentryTrait
关于问题 - 为什么特征,正如我所看到的,这是因为特征是可堆叠的,以便解决着名的diamond problem
trait Base { def x: Unit = () }
trait A extends Base { override def x: Unit = { println("A"); super.x}}
trait B extends Base { override def x: Unit = { println("B"); super.x}}
class T1 extends A with B {}
class T2 extends B with A {}
(new T1).x // Outputs B then A
(new T2).x // Outputs A then B
即使特性A
super为Base
(对于T1
),它也会调用B
实现而不是Base
。这是由于trait linearization
所以对于类,如果你扩展一些东西 - 你可以确定下一个将调用这个基础。但这对于特质来说并非如此。这可能就是你没有特质构造函数参数的原因
答案 1 :(得分:2)
在特征中具有构造函数和状态(然后使其成为类)的问题是具有多重继承。虽然这在假设语言中在技术上是可行的,但对于语言定义和理解程序代码来说是很糟糕的。在对此问题的其他回答中提到的菱形问题,导致最高级别的基类构造函数被调用两次(下例中的A的构造函数)。
在类似Scala的语言中考虑此代码,该语言允许多重继承:
Class A(val x: Int)
class B extends A(1)
class C extends A(2)
class D extends B, C
如果包含state,那么你必须在A类中拥有值x的两个副本。所以你有两个A类副本(或者一个副本和钻石问题 - 因为UML的钻石形状而被称为继承图)。
早期版本的C ++编译器(称为C-Front)有很多错误,编译器或编译代码经常崩溃处理它们。问题包括如果您有对B或C的引用,您(实际上是编译器)如何确定对象的开始?编译器需要知道为了将对象从Base类型(在下图中,或上图中的A)转换为Descendant类型(上图中的D)。
Multiple Inheritance Memory Layout
但是,这适用于特质吗?我理解它的方式,Traits是使用委托模式实现组合的简单方法(我假设你们都知道GoF模式)。当我们用任何其他语言(Java,C ++,C#)实现Delegation时,我们保持对另一个对象的引用,并通过调用其类中的方法将消息委托给它。如果通过简单地保留引用并调用其方法在Scala内部实现traits,那么traits与Delegation完全相同。那么,为什么它有一个构造函数呢?我认为它应该能够有一个不违反其意图。
答案 2 :(得分:0)
问题应该是:为什么我们需要Scala中的类?马丁奥德斯基曾表示,斯卡拉可以通过一些特质来应对。我们需要向traits添加构造函数,以便可以构造traits实例。没关系,Odersky说他已经为特质构造函数制定了线性化算法。
真正的目的是平台互操作性。
Scala打算与之集成的几个平台(目前是Java,以前的.NET,可能在将来的Cocoa / Core Foundation / Swift / Objective-C中)有一个独特的类概念,并不总是很容易Scala特征和平台类之间的1:1映射。例如,这与接口不同:平台接口和Scala特征之间存在一个简单的映射 - 只有抽象成员的特征与接口同构。
类,包和null
是Scala功能的一些示例,其主要目的是平台集成。
Scala设计师非常努力地保持语言小巧,简单和正交。但Scala也明确希望与现有平台很好地集成。实际上,尽管Scala本身就是一门优秀的语言,但专门设计为主要平台语言的替代品(Java平台上的Java,.NET平台上的C#)。为了做到这一点,必须做出一些妥协:
null
。在与现实Java代码进行互操作时,无法以任何合理的方式在null
和Option
之间自动映射。你必须在Scala中拥有null
,即使我们希望它不在那里。答案 3 :(得分:-1)
我能想到的唯一真正的区别是特征不能有构造函数参数。但那有什么意义呢?我的意思是,为什么不呢?
考虑
trait A(val x: Int)
trait B extends A(1)
trait C extends A(2)
class D extends B with C
(new D {}).x
应该是什么?注意:Scala 3中有plans to add trait parameters,但仍有限制,因此不允许使用上述内容。