考虑以下情况:
trait A {
protected val mydata = ???
def f(args) = ??? //uses mydata
}
class B
class C
class D(arg1: String) extends B with A {
override val mydata = ??? /// some calculation based on arg1
}
class E(arg1: String) extends C with A{
override val mydata = ??? /// some calculation based on arg1
}
A必须是一个特征,因为它被不同的不相关的类使用。问题是如何实现mydata的定义。
标准方式(在许多地方建议将mydata定义为def并在子节点中覆盖它。但是,如果f假定mydata永远不会改变那么当某个子节点扩展时,调用之间的函数会改变,这会导致问题与val。
另一种方法是:
trait A {
protected val mydata = g
protected def g()
}
这个问题(除了添加另一个函数之外)的问题是,如果g依赖于子代中的构造变量,那么这些必须成为子代的成员(例如,如果数据很大并且在构造中给出,则可能是一个问题):
class D(arg1: Seq[String]) {
def g() = ??? // some operation on arg1
}
如果我将val作为摘要保留在特征中,我可以找到诸如here}之类的问题。
我正在寻找的是一种定义子项中val值的方法,确保它是一个val并且无需为后期计算保存数据。类似于在java中我可以定义一个最终的val并在构造函数中填充它
答案 0 :(得分:1)
标准方式(在许多地方建议将mydata定义为def并在子节点中覆盖它......如果我将val作为抽象放在特征中,我可以达到诸如此处找到的问题)。
这是一个常见的误解,也在相关问题的公认答案中显示。问题是实施作为val
,无论如何都需要它。具有被覆盖的具体val
只会使其变得更糟:抽象的至少可以由lazy val
实现。避免此问题的唯一方法是确保在mydata
或其子类型的构造函数中直接或间接访问A
,直到它被初始化为止。在f
中使用它是安全的(假设f
未在构造函数中调用,这将是对mydata
的间接访问。
如果您能确保这一要求,那么
trait A {
protected val mydata
def f(args) = ??? //uses mydata
}
class D(arg1: String) extends B with A {
override val mydata = ??? /// some calculation based on arg1
}
class E(arg1: String) extends C with A{
override val mydata = ??? /// some calculation based on arg1
}
正是正确的定义。
如果你不能,那么你必须忍受你的最后一个解决方案,尽管有缺点,但mydata
需要lazy
以避免类似的初始化顺序问题,这已经给出了同样的缺点独自一人。