在带有类型注释的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()
}
你有什么理由比另一个更喜欢? - 我个人喜欢第二个,因为'覆盖'关键字清楚地表达了正在发生的事情。
答案 0 :(得分:0)
你正在混淆一些彼此相对正交的概念,即使它们都允许你与Scala的OO模型进行某种形式的交互,即:
trait
s和abstract class
es def
和val
s override
限定符您可能会注意到,对于您的特定示例,编译器不会禁止您互换使用trait
或abstract class
,以及添加和删除override
限定符是否具体类实现trait
或abstract class
。你唯一不能做的就是从一个非具体的基类中实现def
,它将成员定义为def
(后面会详细介绍)。
让我们一次一个地理解这些概念。
trait
s和abstract class
es trait
和abstract class
之间的主要区别之一是前者可用于多重继承。您可以从一个或多个trait
,abstract 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
,因为这使您可以自由地在具体实现中将其定义为def
或val
。
以下是合法:
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
限定符仅在扩展的class
或trait
已经提供您要覆盖的特定字段或方法的具体实现时才是必需的。 以下代码无法编译,编译器会告诉您override
关键字是必需的:
trait Trait {
def member = "Trait"
}
class Class extends Trait {
val member = "Class"
}
但正如我已经提到的那样,正如您已经指出的那样,override
关键字对于澄清被覆盖的内容和不被覆盖的内容非常有用,因此强烈建议您使用它,即使它不是绝对必要的
val
和def
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} }第