替代在构造函数中执行大量计算 - scala

时间:2012-03-07 12:33:33

标签: scala constructor

我正在学习新项目的scala,尽可能地追求不变性和功能风格。

我正在创建的其中一个对象在其构造函数中接受了大量输入,然后重复应用大量计算来生成相关输出,这些输出作为字段存储在对象上。

在执行计算并将结果添加到内部的可变ListBuffer时,关于对象的所有其他内容都是不可变的 - 一旦创建,您就无法更改任何输入值并再次运行计算显然会产生同样的结果。

但是,在构造函数中进行如此多的计算似乎并不合适。我能看到的唯一方法是让计算值为var s并提供执行计算的run方法 - 但是这个方法可以被多次调用,这是没有意义的。

在scala构造函数中做很多事情真的很好吗?例如,没有对DB的调用,只是内部计算。或者有一些模式吗?

这是非常简单的基本概念:

class Foo(val x:Int, val y:Int, calculations:List[Calculation]) {
  val xHistory = new collection.mutable.ListBuffer[Int]()
  val yHistory = new collection.mutable.ListBuffer[Int]()

  calculations.map { calc => calc.perform(this) }.foreach { result => 
    xHistory += result.x 
    yHistory += result.y
  }
}

基本上我希望输入包装在一个Foo对象的方便实例中,这样我就可以将它传递给各种计算策略(每个计算策略可能需要不同的输入组合)。

3 个答案:

答案 0 :(得分:6)

在构造函数内部工作

通常我在构造函数中做了昂贵的东西。但请注意,评论中提到构造函数代码可能不太优化(在此处插入Java实现,这是正确的)。如果您有多线程应用程序,也请阅读下一段。

延迟初始化

我不知道在构造函数中做大量工作可能有什么问题。

正如评论中所指出的,构造函数内部运行的代码可能存在问题。因此,Scala 2.8.0中引入了DelayedInit特征。例如,在使用Swing GUI元素时会发生此类问题。

  

DelayedInit特征提供了另一种自定义工具   类和对象的初始化序列。如果是一个类或对象   继承自这个特性,它的所有初始化代码都打包在一个   闭包并作为参数转发给名为delayedInit的方法   它被定义为trait DelayedInit中的抽象方法。

     

delayedInit的实现因此在执行时具有完全的自由度   初始化代码。例如,Scala的新App特质商店   内部缓冲区中的所有初始化序列并执行它们   当调用对象的main方法时。

懒惰构造

要以不同的方式延迟计算,您可以使用以下方法,这也解决了并发问题:

  • 您可以使用lazy val成员,这些成员将在首次请求时进行计算。
  • 如果计算一系列昂贵的对象,您可能希望使用像Stream这样的“惰性”数据结构。这类似于List,只根据需要计算下一个元素。因此,在某个时间点,只计算了已经访问过的Stream的初始部分。

使用lazy

的指示

您可能想要做的另一个考虑因素是计算值 是否会被使用。如果可能不需要它们,那么使用我描述的惰性方法就是要走的路。另一方面,如果你肯定访问这些昂贵的成员,我认为在构造函数中进行计算没有任何问题,使用惰性成员可能会增加不必要的计算开销。

关于OP示例

的注释

在构造函数中做危险的事情是不对的;比如让部分构造的对象的引用转义构造函数(通过this - 引用)。 OP内部的示例使用calc.perform(this)执行此操作。这个“潜在的错误”无法通过以下建议来解决。

答案 1 :(得分:4)

创建一个执行所有必要计算的工厂方法并返回用计算值预先填充的新不可变实例怎么样?您可以使用伴随对象使工厂方法关闭实际对象。

object Foo {
    def create(input: ...) {
        val output = //long running computations
        new Foo(output)
    }
}

class Foo(val output: ...)

您可能希望按照 @Nicolas 的建议隐藏Foo class构造函数:

class Foo private (val output: ...)

现在你可以写:

val foo: Foo = Foo.create(input)

答案 2 :(得分:2)

一种可能性是将计算封装在值初始值设定项

class Foo(arg1:String, arg2:String){

val (x, y, z) = {  //tedious calculation using inputs
                        (result1, result2, result:3)}

}

如果消耗它们可能是可选的,或者如果在第一次使用时而不是构造时计算性能更为明智,则可以使生成的值变得懒惰。