我有大量简单的命令行Scala应用程序,它们共享相当多的常见结构。所有这些都继承自scala.App,这很好。我想将这些命令行应用程序的共享结构重构为一个共同特征,然后我可以将其继承到我的(更简单的)命令行应用程序类中。问题出现在一些常见结构中包括解析命令行参数。
object MyScript extends BaseScript with App{
//small bits of business logic using components defined in BaseScript
}
trait BaseScript extends App{
val configuration = loadConfiguration(args(0))
//setup a bezillion components, usable from any of the scripts, based on the configuration
}
这会编译,但是当实际解除引用args
时,NPE会失败,可能是因为App
特征尚未初始化。更改特征顺序并将BaseScript中的App继承更改为自我类型声明不做任何操作,就像使用DelayedInit进行实验一样。在BaseScript中将组件声明为“懒惰”可以工作,但我还希望在初始化期间实际使用这些组件(例如,设置日志目录并根据配置加载JDBC驱动程序类),因此懒惰的好处就会丢失。我能做些什么来使命令行参数在BaseScript特性中可见和初始化?
答案 0 :(得分:4)
我认为你最好的选择是将你的BaseScript
特征改为一个类,原因有两个。第一个是与类相比,特征初始化以相反的顺序执行。见this question on initialization behavior。其次,BaseScript
在语义上比其他行为更像是一个超类。我想你会发现这可以简化事情。
执行MyScript
时,以下代码首先初始化BaseScript
类。 BaseScript
依次依赖于App
特征并强制它首先初始化。
object MyScript extends BaseScript {
//small bits of business logic using components defined in BaseScript
println( "running" )
println( "arg(0): " + configuration )
}
class BaseScript extends App {
val configuration = loadConfiguration(args)
//setup a bezillion components, usable from any of the scripts, based on the configuration
def loadConfiguration( args: Array[String] ) = {
println( "configuring" )
if ( args.length > 0 ) args(0) else null
}
}
答案 1 :(得分:3)
您是否尝试过使用lazy val(而不是扩展App
特征)?
trait BaseScript { self : App =>
lazy val configuration = loadConfiguration(args(0))
//setup a bezillion components, usable from any of the scripts
//based on the configuration
}
答案 2 :(得分:1)
查看App
source,您似乎可以在应用代码运行之前覆盖main
以使用args执行操作:
trait AppUtil extends App {
def myInit(args: Array[String]) {
println("args " + args.size)
}
override def main(args: Array[String]) {
myInit(args)
super.main(args)
}
}
我怀疑App
来源可以为您提供重写自定义App
的灵感。代码真的不长,您可以更好地控制事物,例如您使用args
执行的操作,运行前后的事件main
。