为什么我们需要scala中的特征?

时间:2016-03-18 11:07:21

标签: scala traits

所以,我试图建立一个finagle服务器,与哨兵交谈(不重要),偶然发现一个案例,我需要在同一个案例中继承两个(不是特征)时间,让我们称他们为class SentryHandler extends Handlerclass 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) 

4 个答案:

答案 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的钻石形状而被称为继承图)。

Diamond Multiple Inheritance

早期版本的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#)。为了做到这一点,必须做出一些妥协:

  • Scala有类,即使它们是traits的冗余(假设我们将构造函数添加到traits中),因为很容易将Scala类映射到平台类,并且几乎不可能将traits映射到平台类。只需看看Scala必须跳过的箍,将特性编译为高效的JVM字节码。 (对于每个特性,都有一个包含API的接口和一个包含方法的静态类。对于混合了特征的每个类,生成一个转发器类,它将方法调用转发给属性方法的静态类。性状。)
  • Scala有包,即使它们对象是多余的。 Scala包可以简单地映射到Java包和.NET命名空间。物品不能。
  • 包对象是一种克服包的一些限制的方法,如果我们没有包,我们就不需要包对象。
  • 类型擦除。在编译到JVM时,完全可以保留泛型类型,例如:你可以将它们存储在注释中。但是第三方Java库无论如何都会删除它们的类型,而其他语言也不会理解注释并将Scala类型视为已删除,所以无论如何你必须处理Type Erasure,如果你必须这样做,那么两个为什么呢?
  • 当然是
  • null。在与现实Java代码进行互操作时,无法以任何合理的方式在nullOption之间自动映射。你必须在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,但仍有限制,因此不允许使用上述内容。