我在Scala中使用外部库,它使用一组特征将复杂的配置选项传递给不同的方法。这是Highcharts Scala API,但问题似乎更为笼统。
库定义了一个特征(实际使用中的HighchartsOptions),它只是一个数据传输对象,它存储了许多字段并允许它们被传递。为简洁起见,简化和概括的代码如下所示:
trait Opts {
def option1: Int = 3
def option2: String = "abc"
//Many more follow, often of more complex types
}
只要可以在一个地方生成完整的选项集,就可以使用简洁的语法:
val opts = new Opts() {
override val option1 = 5
//And so on for more fields
}
doSomething(opts)
但是,在某些情况下,一段代码会准备这样的配置,但另一段代码需要额外调整一个选项。能够将一些Opts
实例传递给方法并让该方法修改一个或两个值会很好。
由于原始特征基于def
而不是var
,因此只有在知道对象的类型时才能轻松覆盖选项的值,如上例所示。 如果方法只接收Opts
的某个匿名子类的实例,它如何创建另一个实例或修改收到的实例以便调用例如option2
。 HighchartsOptions
可以返回不同的值吗?所需的操作类似于Mockito's spy
所做的操作,但我觉得应该有一些不那么人为的方法,而不是使用模拟框架来实现这种效果。< / p>
PS:实际上我对图书馆作者使用这样的界面感到有点惊讶,所以也许我错过了一些东西,并且有一些完全不同的方法来实现我从几个方面构建一组选项的目标代码中的不同位置(例如,一些可变的构建器对象,我可以传递而不是完成的{{1}})?
答案 0 :(得分:2)
我首先要检查是否使用Opts特性(单独)是绝对必要的。希望它不是,然后你就像你所说的那样扩展特性,用变量覆盖defs。
当Opts是强制性的并且您想要复制某个字段的实例时,您可以执行以下操作:
为Opts编写一个包装器,它扩展了Opts,但是委托每次调用包含的Opts,不包括你想要修改的字段。将这些字段设置为您想要的值。 为宽接口特性编写包装器可能会很无聊,因此您可以考虑使用http://www.warski.org/blog/2013/09/automatic-generation-of-delegate-methods-with-macro-annotations/让宏自动生成大部分内容。
答案 1 :(得分:1)
最简单的解决方案是允许特征定义它自己的“复制”方法,并允许它的子类(甚至基类)使用它。但是,参数实际上只能匹配基类,除非您稍后重新编写它。顺便说一句,这不是“混入”,所以你的root也可能是一个抽象类,但它的工作方式相同。这一点是子类类型在复制时不断传递。
(抱歉,我没有编译器就输入了这个,所以可能需要一些工作)
trait A {
type myType<:A
def option1: Int
def option2: String
def copyA(option1_:Int=option1, option2_String=option2):myType = new A {
def option1 = option_1
def option2 = option_2
}
}
trait B extends A { me=>
type myType = B
def option3: Double
//callable from A but properly returns B
override def copyA(option1_:Int=option1, option2_:String=option2):myType = new B {
def option1 = option_1
def option2 = option_2
def option3 = me.option3
}
//this is only callable if you've cast to type B
def copyB(option1_:Int=option1, option2_:String=option2, option3_:Double=option3):myType = new B {
def option1 = option_1
def option2 = option_2
def option3 = option_3
}
}
答案 2 :(得分:1)
最短,最简单的方式。
定义案例类:
case class Options(
option1: Int,
option2: String
/* ... */
) extends Opts
从Opts隐式转换为您的选项
object OptsConverter {
implicit def toOptions(opts: Opts) = Options(
option1 = opts.option1,
option2 = opts.option2
/* ... */
)
}
这样你就可以免费获得所有复制方法(由编译器生成)。 您可以这样使用它:
import OptsConverter.toOptions
def usage(opts: Opts) = {
val improvedOpts = opts.copy(option2 = "improved")
/* ... */
}
注意,Options扩展了Opts,因此您可以在需要Opts时使用它。您可以在导入隐式转换的每个位置调用copy以获取已修改的Opts实例。