3

我在 Scala 中使用了一个外部库,它使用一组特征将复杂的配置选项传递给不同的方法。这是 Highcharts Scala API,但问题似乎更普遍。

该库定义了一个trait(实际使用中的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实例传递给一个方法并让该方法修改一个或两个值会很好。

由于原始特征基于defs 而不是vars,因此只有在对象类型已知的情况下才可以轻松覆盖选项的值,就像上面的示例一样。如果一个方法只接收 的某个匿名子类的实例,Opts它如何创建另一个实例或修改接收到的实例,以便调用 egoption2可以返回不同的值?所需的操作类似于Mockito 的spy操作,但是我觉得应该有一些比使用模拟框架来实现此效果的方式更不做作。

PS:实际上我对图书馆的作者使用这样的界面感到有点惊讶,所以也许我遗漏了一些东西,并且有一些完全不同的方法可以实现我的目标,即从几个不同的地方构建一组选项代码(例如,一些可变的构建器对象,我可以传递而不是完成HighchartsOptions)?

4

3 回答 3

2

我会首先检查(仅)使用 Opts 特征是否是绝对必要的。希望不是,然后您只需扩展 trait,用 var 覆盖 def,就像您说的那样。

当 Opts 是强制性的并且您想要复制修改某些字段的实例时,您可以执行以下操作:

为 Opts 编写一个包装器,它扩展了 Opts,但将每次调用委托给包装的 Opts,不包括您要修改的字段。将这些字段设置为您想要的值。为宽接口特征编写包装器可能是一项无聊的任务,因此您可以考虑使用http://www.warski.org/blog/2013/09/automatic-generation-of-delegate-methods-with-macro-annotations /让宏自动生成大部分内容。

于 2013-09-25T21:48:35.803 回答
1

最简单的解决方案是允许特征定义它自己的“复制”方法,并允许它的子类(甚至基类)使用它。但是,参数实际上只能匹配基类,除非您稍后重铸它。顺便说一句,这不能作为“混合”工作,因此您的根也可能是一个抽象类,但它的工作方式相同。关键是子类类型在被复制时不断传递。

(对不起,我在没有编译器的情况下输入了这个,所以它可能需要一些工作)

    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         
            }


        }
于 2013-09-25T21:48:40.897 回答
1

最短、最简单的方法。

定义一个案例类:

case class Options(
  option1: Int,
  option2: String
  /* ... */
) extends Opts

以及从 Opts 到您的 Options 的隐式转换

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 的修改实例。

于 2013-09-26T07:21:25.983 回答