首页 文章

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

提问于
浏览
1

在带有类型注释的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()
}

任何理由你更喜欢一个? - 我个人喜欢第二个,因为'override'关键字清楚地表达了正在发生的事情 .

1 回答

  • 0

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

    • trait s和 abstract class es

    • abstract defval s

    • override 限定符

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

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

    特征和抽象类

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

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

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

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

    抽象defs和vals

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

    以下是 legal

    trait Trait {
      def member: String
    }
    
    class Class extends Trait {
      val member = "Class"
    }
    

    以下 doesn't compile

    trait Trait {
      val member: String
    }
    
    class Class extends Trait {
      def member = "Class"
    }
    

    覆盖限定符

    由于您提到的原因,强烈建议使用 override ,但请注意,这不一定使用 val . 实际上, the following is legal 代码:

    trait Trait {
      val member: String
    }
    
    class Class extends Trait {
      val member = "Class"
    }
    

    仅当扩展 classtrait 已提供您要覆盖的特定字段或方法的具体实现时, override 限定符才是必需的 . The following code does not compile 并且编译器会告诉您 override 关键字是必需的:

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

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


    vals和defs的奖金

    traittrait 中使用 val 的另一个好理由是防止使其继承的顺序有意义,在现实情况下,当 trait 也提供具体实现时,可能会导致一些棘手的问题 .

    请考虑以下代码(您也可以使用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 必须在施工时进行评估,因此它们的副作用也是如此:注意施工行为如何变化,操作在不同时间执行,即使这两个类延伸相同的 trait .

相关问题