我最近一直在研究基于Apache POI功能的DSL风格的库包装器,并面临着一个我似乎无法解决的挑战。
该库的目标之一是为用户提供将电子表格模型构建为不可变对象集合的能力,即
val headerStyle = CellStyle(fillPattern = CellFill.Solid, fillForegroundColor = Color.AquaMarine, font = Font(bold = true))
val italicStyle = CellStyle(font = Font(italic = true))
有以下假设:
CellStyle
以及明确指定参数的完整列表); 第二点很重要,因为我想将这个数据模型转换成多种格式,即Excel中的默认字体不必与HTML浏览器中的默认字体相同(如果用户没有&#39 ; t明确定义字体系列我希望他使用这些默认值来查看数据。
为了处理这些要求,我使用了此处描述的空模式的变体:Pattern for optional-parameters in Scala using null,并在此处提出Scala default parameters and null(在简化示例下方)。
object ModelObject {
def apply(modelParam : String = null) : ModelObject = ModelObject(
modelParam = Option(modelParam)
)
}
case class ModelObject private(modelParam : Option[String])
由于null仅在伴随对象内部使用并且非常本地化,因此为了解决方案的简单性,我决定接受空牺牲。该模式适用于所有引用类。
但是对于Scala原始类型,无法指定包装器null。这对布尔来说尤其是一个巨大的问题,我有效地考虑了3个状态(true,false和undefined)。想要提供界面,用户仍然可以写bold = true
我决定使用接受空值的Java包装器。
object ModelObject {
def apply(boolParam : java.lang.Boolean = null) : ModelObject = ModelObject(
boolParam = Option(boolParam).map(_.booleanValue)
)
}
case class ModelObject private(boolParam : Option[Boolean])
然而,这并不正确,我一直在想是否有更好的方法解决问题。我一直在考虑定义联合类型(附加对象表示未定义的值):How to define "type disjunction" (union types)?,但是由于未明确的状态不应该显式地使用IDE向用户公开的参数类型,它会非常混乱(理想情况下我喜欢它是布尔值)。
有没有更好的方法来解决这个问题?
更多信息:
答案 0 :(得分:3)
您可以使用我在此处描述的模式的变体:How to provide helper methods to build a Map
总结一下,你可以使用一些辅助泛型类来表示可选参数(很像Option
)。
abstract sealed class OptArg[+T] {
def toOption: Option[T]
}
object OptArg{
implicit def autoWrap[T]( value: T ): OptArg[T] = SomeArg(value)
implicit def toOption[T]( arg: OptArg[T] ): Option[T] = arg.toOption
}
case class SomeArg[+T]( value: T ) extends OptArg[T] {
def toOption = Some( value )
}
case object NoArg extends OptArg[Nothing] {
val toOption = None
}
您可以像这样使用它:
scala>case class ModelObject(boolParam: OptArg[Boolean] = NoArg)
defined class ModelObject
scala> ModelObject(true)
res12: ModelObject = ModelObject(SomeArg(true))
scala> ModelObject()
res13: ModelObject = ModelObject(NoArg)
但是,您可以看到OptArg
类本身ModelObject
现已泄漏(boolParam
被输入OptArg[Boolean]
而不是Option[Boolean]
。
解决这个问题(如果对您来说很重要)只需要像您自己一样定义一个单独的工厂:
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class ModelObject private(boolParam: Option[Boolean])
object ModelObject {
def apply(boolParam: OptArg[Boolean] = NoArg): ModelObject = new ModelObject(
boolParam = boolParam.toOption
)
}
// Exiting paste mode, now interpreting.
defined class ModelObject
defined module ModelObject
scala> ModelObject(true)
res22: ModelObject = ModelObject(Some(true))
scala> ModelObject()
res23: ModelObject = ModelObject(None)
UPDATE 使用此模式的优势,而不是简单地定义@drexin所示的几个重载apply
方法,在后一种情况下,重载次数会随着数量的增长而快速增长参数(2 ^ N)。如果ModelObject
有4个参数,那就意味着手写16个重载!