什么是Scala等同于Java构build器模式?

在Java中我每天都在做的工作中,我使用了很多new PizzaBuilder(Size.Large).onTopOf(Base.Cheesy).with(Ingredient.Ham).build();界面的构build器,例如: new PizzaBuilder(Size.Large).onTopOf(Base.Cheesy).with(Ingredient.Ham).build();

使用快捷的Java方法,每个方法调用都会改变构build器实例并返回。 毫无疑问,它涉及更多的打字,在修改之前首先克隆build造者。 构build方法最终在build设者状态上做了大量的工作。

什么是在Scala中实现相同的好方法?

如果我想确保onTopOf(base:Base)只被调用一次,然后只with(ingredient:Ingredient)build():Pizza可以被称为,a-la是一个定向的build设者,我将如何去接近这个?

Scala 2.8中Builder模式的另一种替代方法是使用具有默认参数和命名参数的不可变大小写类。 它有点不同,但效果是聪明的默认值,指定的所有值和东西只能指定一次语法检查…

以下使用string为简洁/速度值…

 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) 

你也可以使用现有的不可变的实例作为build设者…

 scala> val lp2 = p3.copy(base = "Large") lp2: Pizza = Pizza(List(Ham, Pineapple),Large,Edam) 

这里有三个主要的select。

  1. 使用与Java,类和全部相同的模式。

  2. 使用命名参数和默认参数以及复制方法。 案例类已经为您提供了这个,但这里是一个不是案例类的例子,只是为了让您更好地理解它。

     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 
  3. 使用types安全的生成器模式 。 我所知道的最好的介绍是这个博客 ,其中还包含许多关于这个主题的其他文章的参考。

    基本上,types安全构build器模式在编译时保证提供所有必需的组件。 人们甚至可以保证相互排斥select或礼貌。 成本是build设者代码的复杂性,但…

这是一样的确切模式。 斯卡拉允许突变和副作用。 也就是说,如果你想更纯粹一些,每个方法都会返回一个你正在构造的对象的一个​​新实例,并且元素被修改。 你甚至可以把这个函数放在一个类的对象中,这样你的代码就有了更高层次的分离。

 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) 

(注意:我可能在这里搞了一些语法。)

Case类解决了以前的答案所示的问题,但是如果在对象中有scala集合,则最终的api很难从java中使用。 为java用户提供一个stream畅的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部分适用是可行的,如果你正在构build一个小的对象,你不需要通过方法签名。 如果任何这些假设不适用,我build议使用可变的构build器来构build一个不可变的对象。 有了这个scala,你可以用一个case类来实现builder模式,这个对象可以用一个同伴作为构build器来构build。

鉴于最终的结果是一个构build的不可变的对象,我没有看到它击败了任何斯卡拉原则。