在带有类型注释的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 回答
你正在混合一些彼此相对正交的概念,即使它们都允许你与Scala的OO模型进行某种形式的交互,即:
trait
s和abstract class
esabstract
def
和val
soverride
限定符您可能会注意到,对于您的特定示例,编译器不会禁止您交替使用
trait
或abstract class
,以及添加和删除override
限定符,无论具体类是实现trait
还是abstract class
. 你唯一不能做的就是从一个非具体的基类中实现一个def
,它将成员定义为def
(后面会详细介绍) .让我们一次一个地理解这些概念 .
特征和抽象类
trait
和abstract class
之间的主要区别之一是前者可用于多重继承 . 您可以从一个或多个trait
,abstract 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,因为这使您可以自由地将其定义为具体实现中的def
或val
.以下是 legal :
以下 doesn't compile :
覆盖限定符
由于您提到的原因,强烈建议使用
override
,但请注意,这不一定使用val
. 实际上, the following is legal 代码:仅当扩展
class
或trait
已提供您要覆盖的特定字段或方法的具体实现时,override
限定符才是必需的 . The following code does not compile 并且编译器会告诉您override
关键字是必需的:但正如我已经提到的那样,正如你已经提到的那样,
override
关键字对于澄清被覆盖的内容和不被覆盖的内容非常有用,所以强烈建议你使用它,即使它不是绝对必要的 .vals和defs的奖金
trait
在trait
中使用val
的另一个好理由是防止使其继承的顺序有意义,在现实情况下,当trait
也提供具体实现时,可能会导致一些棘手的问题 .请考虑以下代码(您也可以使用here on Scastie运行和播放):
由于
val
必须在施工时进行评估,因此它们的副作用也是如此:注意施工行为如何变化,操作在不同时间执行,即使这两个类延伸相同的trait
.