Scala DI使用trait vs抽象类与自我类型注释?

时间:2018-05-31 15:06:40

标签: scala

在带有类型注释的Scala依赖注入中,注入的类/对象引用可以实现为def trait成员或val抽象成员,如:

trait InjectedTrait {}

class InjectedClass extends InjectedTrait {}

trait TestTrait {
    def injectedTrait: InjectedTrait
}

class TestClass {
    this: TestTrait =>
}

// In main()
val obj = new TestClass() with TestTrait {
        val injectedTrait = new InjectedClass()
}

abstract class AbstractInjectedClass {}

class InjectedClass extends AbstractInjectedClass {}

trait TestTrait {
    val injectedClass: AbstractInjectedClass
}

class TestClass {
    this: TestTrait =>
}

// In main()
val obj = new TestClass() with TestTrait {
    override val injectedClass = new InjectedClass()
}

你有什么理由比另一个更喜欢? - 我个人喜欢第二个,因为'覆盖'关键字清楚地表达了正在发生的事情。

1 个答案:

答案 0 :(得分:0)

你正在混淆一些彼此相对正交的概念,即使它们都允许你与Scala的OO模型进行某种形式的交互,即:

  • trait s和abstract class es
  • 抽象defval s
  • override限定符

您可能会注意到,对于您的特定示例,编译器不会禁止您互换使用traitabstract class,以及添加和删除override限定符是否具体类实现traitabstract class。你唯一不能做的就是从一个非具体的基类中实现def,它将成员定义为def(后面会详细介绍)。

让我们一次一个地理解这些概念。

trait s和abstract class es

traitabstract class之间的主要区别之一是前者可用于多重继承。您可以从一个或多个traitabstract class和一个或多个trait或一个abstract class继承。

另一个是abstract class es可以有构造函数参数,这使得处理具有仅在运行时可用的参数的对象的动态构造更有趣。

在决定使用哪一个时,您应该推断这些特征。在DI用例中,由于您可能想要建立一个引用多种类型的自我类型注释(例如:self => Trait1 with Trait2),trait s往往会给您更多的自由并且通常会受到青睐。

在这一点上值得注意的是,历史上abstract class es更倾向于与JVM(因为它们具有类似的Java构造)更好地交互,而在Scala 3中(目前正在以的名义开发) Dotty trait将有可能获得构造函数参数,使它们比abstract class更强大。

抽象def s和val s

始终建议您将抽象成员定义为def,因为这使您可以自由地在具体实现中将其定义为defval

以下是合法

trait Trait {
  def member: String
}

class Class extends Trait {
  val member = "Class"
}

以下无法编译

trait Trait {
  val member: String
}

class Class extends Trait {
  def member = "Class"
}

override限定符

由于您提到的原因,强烈建议使用override,但请注意,这不一定要使用val。事实上,以下是合法的代码:

trait Trait {
  val member: String
}

class Class extends Trait {
  val member = "Class"
}

override限定符仅在扩展的classtrait已经提供您要覆盖的特定字段或方法的具体实现时才是必需的。 以下代码无法编译,编译器会告诉您override关键字是必需的:

trait Trait {
  def member = "Trait"
}

class Class extends Trait {
  val member = "Class"
}

但正如我已经提到的那样,正如您已经指出的那样,override关键字对于澄清被覆盖的内容和不被覆盖的内容非常有用,因此强烈建议您使用它,即使它不是绝对必要的

valdef s

的奖励

val中使用trait的另一个好理由是阻止使其继承的顺序有意义,在现实场景中可以导致当trait s也提供具体的实施时,对于一些强硬的头脑。

考虑以下代码(您也可以使用here on Scastie运行和播放):

trait HasTrunk {
  val trunk = {
    println("I have a trunk")
    "trunk"
  }
}

trait HasLegs {
  val legs = {
    println("I have legs")
    "legs"
  }
}

final class Elefant extends HasTrunk with HasLegs
final class Fly extends HasLegs with HasTrunk

new Elefant
new Fly

由于val必须在施工时进行评估,因此它们的副作用也是如此:注意施工行为如何变化,操作在不同时间执行,即使这两个类延伸相同{{1} }第