What is Scala equivalent to a Java builder pattern?

In the work that I do every day in Java, I often use builders for a free interface, for example: new PizzaBuilder(Size.Large).onTopOf(Base.Cheesy).with(Ingredient.Ham).build();

Thanks to the fast and dirty Java approach, every method call mutates the linker instance and returns this . Invariably, it involves more typing, cloning the builder before changing it. The assembly method ultimately makes a heavy climb over the state of the builder.

What is a good way to achieve the same in Scala?

If I wanted to make sure that onTopOf(base:Base) was called only once, and then I could only call with(ingredient:Ingredient) and build():Pizza , a-la - directional constructor, as I would like get closer to this?

+47
java design-patterns scala
Jan 07 2018-11-12T00:
source share
5 answers

Another alternative to the Builder pattern in Scala 2.8 is to use immutable case classes with default arguments and named parameters. Its a little different, but the effect is smart defaults, all the specified values โ€‹โ€‹and things that are specified only once with syntax checking ...

Below are used strings for values โ€‹โ€‹for brevity / speed ...

 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) 

Then you can also use existing immutable instances as your own constructors ...

 scala> val lp2 = p3.copy(base = "Large") lp2: Pizza = Pizza(List(Ham, Pineapple),Large,Edam) 
+50
Jan 07 2018-11-11T00:
source share
โ€” -

Here you have three main alternatives.

  • Use the same template as in Java, classes and all.

  • Use the named and default arguments and the copy method. Class classes already provide this for you, but here is an example that is not a case class, so you can understand it better.

     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 
  • Use a secure type template pattern. The best introduction I know of is this blog , which also contains links to many other articles on this subject.

    Basically, a secure template template ensures that all necessary components are provided at compile time. You can even guarantee mutual exclusion of options or arity. Cost is the builder code complexity, but ...

+25
Jan 07 '11 at 15:05
source share

This is the exact same image. Scala tolerates mutation and side effects. However, if you want to be cleaner, each method returns a new instance of the object that you create with the elements changed. You can even put functions in a class object so that your code has a higher level of separation.

 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) } 

make your code look like

 val myPizza = new Pizza(Large) onTopOf(MarinaraSauce) onTopOf(Cheese) withTopping(Ham) withTopping(Pineapple) 

(Note: I probably messed up some syntax here.)

+8
Jan 07 2018-11-12T00:
source share

Class examples solve the problem, as shown in previous answers, but the resulting api is difficult to use from java when you have scala collections in your objects. To ensure quick use of api for java users, try the following:

 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 } 

Then in java code api is really easy to use

 SEEConfigurationGrammar builder = SEEConfigurationBuilder.empty(); SEEConfiguration configuration = builder .withParameter(new Parameter("name","value")) .withParameter("directGivenName","Value") .withPlugin(new PlugIn("pluginid")) .build(); 
+6
Apr 30 '13 at 5:27
source share

using Scala, partial applications are possible if you create a small object that you do not need in order to pass method signatures. If any of these assumptions does not apply, I recommend using a mutable builder to create an immutable object. With this Scala, you can implement a builder template with the case class for an object that will be created with your companion as a builder.

Given that the end result is a constructed immutable object, I do not see him defeating any of Scala's principles.

0
Jun 02 '16 at 17:59
source share



All Articles