我正在学习新项目的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
对象的方便实例中,这样我就可以将它传递给各种计算策略(每个计算策略可能需要不同的输入组合)。
答案 0 :(得分:6)
通常我在构造函数中做了昂贵的东西。但请注意,评论中提到构造函数代码可能不太优化(在此处插入Java实现,这是正确的)。如果您有多线程应用程序,也请阅读下一段。
我不知道在构造函数中做大量工作可能有什么问题。
正如评论中所指出的,构造函数内部运行的代码可能存在问题。因此,Scala 2.8.0中引入了DelayedInit
特征。例如,在使用Swing GUI元素时会发生此类问题。
DelayedInit
特征提供了另一种自定义工具 类和对象的初始化序列。如果是一个类或对象 继承自这个特性,它的所有初始化代码都打包在一个 闭包并作为参数转发给名为delayedInit的方法 它被定义为trait DelayedInit中的抽象方法。delayedInit的实现因此在执行时具有完全的自由度 初始化代码。例如,Scala的新
App
特质商店 内部缓冲区中的所有初始化序列并执行它们 当调用对象的main方法时。
要以不同的方式延迟计算,您可以使用以下方法,这也解决了并发问题:
lazy val
成员,这些成员将在首次请求时进行计算。Stream
这样的“惰性”数据结构。这类似于List
,只根据需要计算下一个元素。因此,在某个时间点,只计算了已经访问过的Stream
的初始部分。lazy
您可能想要做的另一个考虑因素是计算值 是否会被使用。如果可能不需要它们,那么使用我描述的惰性方法就是要走的路。另一方面,如果你肯定访问这些昂贵的成员,我认为在构造函数中进行计算没有任何问题,使用惰性成员可能会增加不必要的计算开销。
在构造函数中做危险的事情是不对的;比如让部分构造的对象的引用转义构造函数(通过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)}
}
如果消耗它们可能是可选的,或者如果在第一次使用时而不是构造时计算性能更为明智,则可以使生成的值变得懒惰。