您正在混淆一些彼此相对正交的概念,即使它们都允许您与 Scala 的 OO 模型进行某种形式的交互,即:
traits和abstract classes
- 摘要
def和vals
- 限定
override词
您可能会注意到,对于您的特定示例,编译器并没有禁止您互换使用 atrait或 an abstract class,以及添加和删除override限定符,无论具体类实现 atrait还是 an abstract class。你唯一不能做的就是实现一个def非具体的基类,它将成员定义为a def(稍后会详细介绍)。
让我们一次一个地了解这些概念。
traits和abstract classes
traits 和es之间的主要区别之一abstract class是前者可以用于多重继承。trait您可以从一个或多个s、anabstract class和一个或多个traits 或一个继承abstract class。
另一个是abstract classes 可以有构造函数参数,这使得它们在处理具有仅在运行时可用的参数的对象的动态构造时更有趣。
在决定使用哪一个时,您应该对这些特征进行推理。在 DI 用例中,由于您可能希望建立引用多个类型(例如:)的 self 类型注释self => Trait1 with Trait2,因此traits 往往会给您更多的自由,并且通常受到青睐。
在这一点上可能值得注意的是,历史上abstract classes 倾向于与 JVM 更好地交互(因为它们具有类似的构造 Java),而在 Scala 3(目前正在开发中,名为Dotty)trait中,s 将有可能获取构造函数参数,使它们比abstract classes 更强大。
抽象defs 和vals
始终建议您将抽象成员定义为defs,因为这使您可以自由地将其定义为具体实现中的 adef或 a 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词
由于您提到的原因,强烈建议使用 using 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关键字对于阐明哪些被覆盖和哪些未被覆盖非常有用,因此强烈建议您使用它,即使它不是绝对必要的。
vals 和defs 的奖金
另一个不在s 中使用vals 的好理由是防止它们的继承顺序有意义,在现实生活场景中,当s 也提供具体实现trait时,可能会导致一些棘手的问题。trait
考虑以下代码(您也可以在 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
由于vals 必须在构造时评估,它们的副作用也是:注意构造行为如何变化,操作在不同时间执行,即使两个类扩展了相同trait的 s。