在trait中创建一个抽象val的适当的后期初始化

时间:2017-05-18 16:26:22

标签: scala

考虑以下情况:

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并在构造函数中填充它

1 个答案:

答案 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以避免类似的初始化顺序问题,这已经给出了同样的缺点独自一人。