什么是Scala等效于Java构建器模式?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是Scala等效于Java构建器模式?相关的知识,希望对你有一定的参考价值。
在我每天用Java做的工作中,我使用构建器来进行流畅的接口,例如:new PizzaBuilder(Size.Large).onTopOf(Base.Cheesy).with(Ingredient.Ham).build();
使用快速而肮脏的Java方法,每个方法调用都会改变构建器实例并返回this
。不可避免地,它涉及更多的打字,在修改之前首先克隆构建器。构建方法最终会对构建器状态进行繁重的处理。
什么是在Scala中实现相同的好方法?
如果我想确保onTopOf(base:Base)
只被召唤一次,然后只能召唤with(ingredient:Ingredient)
和build():Pizza
,a-la是一名定向建造者,我将如何接近这个?
Scala 2.8中Builder模式的另一种替代方法是使用带有默认参数和命名参数的不可变case类。它有点不同,但效果是智能默认值,所有指定的值和仅通过语法检查指定的内容...
以下使用字符串作为简洁/速度的值......
scala> case class Pizza(ingredients: Traversable[String], base: String = "Normal", topping: String = "Mozzarella")
defined class Pizza
scala> val p1 = Pizza(Seq("Ham", "Mushroom"))
p1: Pizza = Pizza(List(Ham, Mushroom),Normal,Mozzarella)
scala> val p2 = Pizza(Seq("Mushroom"), topping = "Edam")
p2: Pizza = Pizza(List(Mushroom),Normal,Edam)
scala> val p3 = Pizza(Seq("Ham", "Pineapple"), topping = "Edam", base = "Small")
p3: Pizza = Pizza(List(Ham, Pineapple),Small,Edam)
然后,您也可以将现有的不可变实例用作有点构建器......
scala> val lp2 = p3.copy(base = "Large")
lp2: Pizza = Pizza(List(Ham, Pineapple),Large,Edam)
这里有三个主要的选择。
- 使用与Java,类和所有相同的模式。
- 使用命名和默认参数以及复制方法。案例类已经为您提供了这个,但这里的示例不是案例类,只是为了让您更好地理解它。
object Size { sealed abstract class Type object Large extends Type } object Base { sealed abstract class Type object Cheesy extends Type } object Ingredient { sealed abstract class Type object Ham extends Type } class Pizza(size: Size.Type, base: Base.Type, ingredients: List[Ingredient.Type]) class PizzaBuilder(size: Size.Type, base: Base.Type = null, ingredients: List[Ingredient.Type] = Nil) { // A generic copy method def copy(size: Size.Type = this.size, base: Base.Type = this.base, ingredients: List[Ingredient.Type] = this.ingredients) = new PizzaBuilder(size, base, ingredients) // An onTopOf method based on copy def onTopOf(base: Base.Type) = copy(base = base) // A with method based on copy, with `` because with is a keyword in Scala def `with`(ingredient: Ingredient.Type) = copy(ingredients = ingredient :: ingredients) // A build method to create the Pizza def build() = { if (size == null || base == null || ingredients == Nil) error("Missing stuff") else new Pizza(size, base, ingredients) } } // Possible ways of using it: new PizzaBuilder(Size.Large).onTopOf(Base.Cheesy).`with`(Ingredient.Ham).build(); // or new PizzaBuilder(Size.Large).copy(base = Base.Cheesy).copy(ingredients = List(Ingredient.Ham)).build() // or new PizzaBuilder(size = Size.Large, base = Base.Cheesy, ingredients = Ingredient.Ham :: Nil).build() // or even forgo the Builder altogether and just // use named and default parameters on Pizza itself
- 使用类型安全构建器模式。我所知道的最好的介绍是this blog,其中还包含有关该主题的许多其他文章的参考。 基本上,类型安全构建器模式在编译时保证提供所有必需的组件。人们甚至可以保证相互排斥选项或权利。成本是构建器代码的复杂性,但是......
这是完全相同的模式。 Scala允许突变和副作用。也就是说,如果你想要更纯粹,那么让每个方法返回一个你正在构造的对象的新实例,并改变元素。您甚至可以将函数放在类的Object中,以便在代码中实现更高级别的分离。
class Pizza(size:SizeType, layers:List[Layers], toppings:List[Toppings]){
def Pizza(size:SizeType) = this(size, List[Layers](), List[Toppings]())
object Pizza{
def onTopOf( layer:Layer ) = new Pizza(size, layers :+ layer, toppings)
def withTopping( topping:Topping ) = new Pizza(size, layers, toppings :+ topping)
}
这样你的代码可能看起来像
val myPizza = new Pizza(Large) onTopOf(MarinaraSauce) onTopOf(Cheese) withTopping(Ham) withTopping(Pineapple)
(注意:我可能在这里搞砸了一些语法。)
案例类解决了问题,如前面的答案所示,但是当您在对象中有scala集合时,很难从java中使用生成的api。为java用户提供流畅的api试试这个:
case class SEEConfiguration(parameters : Set[Parameter],
plugins : Set[PlugIn])
case class Parameter(name: String, value:String)
case class PlugIn(id: String)
trait SEEConfigurationGrammar {
def withParameter(name: String, value:String) : SEEConfigurationGrammar
def withParameter(toAdd : Parameter) : SEEConfigurationGrammar
def withPlugin(toAdd : PlugIn) : SEEConfigurationGrammar
def build : SEEConfiguration
}
object SEEConfigurationBuilder {
def empty : SEEConfigurationGrammar = SEEConfigurationBuilder(Set.empty,Set.empty)
}
case class SEEConfigurationBuilder(
parameters : Set[Parameter],
plugins : Set[PlugIn]
) extends SEEConfigurationGrammar {
val config : SEEConfiguration = SEEConfiguration(parameters,plugins)
def withParameter(name: String, value:String) = withParameter(Parameter(name,value))
def withParameter(toAdd : Parameter) = new SEEConfigurationBuilder(parameters + toAdd, plugins)
def withPlugin(toAdd : PlugIn) = new SEEConfigurationBuilder(parameters , plugins + toAdd)
def build = config
}
然后在java代码中,api非常容易使用
SEEConfigurationGrammar builder = SEEConfigurationBuilder.empty();
SEEConfiguration configuration = builder
.withParameter(new Parameter("name","value"))
.withParameter("directGivenName","Value")
.withPlugin(new PlugIn("pluginid"))
.build();
如果要构建一个不需要传递方法签名的小对象,则使用Scala partial apply是可行的。如果这些假设中的任何一个不适用,我建议使用可变构建器来构建不可变对象。如果这是scala,您可以使用一个case类来实现构建器模式,以便使用随播作为构建器来构建对象。
鉴于最终结果是一个构造的不可变对象,我没有看到它击败任何Scala原则。
以上是关于什么是Scala等效于Java构建器模式?的主要内容,如果未能解决你的问题,请参考以下文章