Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable composable builder in scala

Tags:

scala

I'm interested how to improve the piece of code bellow. The idea is to build an immutable composable builder. In the end the builder just builds a Map[String, Object]. I want to be able to define core builder components that can be reused and to also let people define their own additional builders to extend the main builder. I'm able to do so but not without the ugly use of reflection. The code looks as follows:

object TestPizzaBuilder {
  def main(args: Array[String]): Unit = {

    val build = new PizzaBuilder()
      .withCheese("blue")
      .withSauce("tomato")
      .build()

    println(build)
  }
}

object PizzaBuilder {
  type BuilderParams = Map[String, Object]
}

class PizzaBuilder(params: BuilderParams = Map.empty) extends BaseBuilder[PizzaBuilder](params: BuilderParams)
  with CheeseBuilder[PizzaBuilder]
  with SauceBuilder[PizzaBuilder] {
}

abstract class BaseBuilder[A <: BaseBuilder[A]](params: BuilderParams)(implicit tag: ClassTag[A]) {

  protected def _copy(tuples: (String, Object)*): A = {
    val constr = tag.runtimeClass.getConstructors()(0)
    constr.newInstance(params ++ tuples).asInstanceOf[A]
  }

  def build(): Map[String, Object] = {
    params
  }
}

trait CheeseBuilder[A <: BaseBuilder[A]] {
  this: BaseBuilder[A] =>
  def withCheese(cheese: String): A = _copy("cheese" -> cheese)
}

trait SauceBuilder[A <: BaseBuilder[A]] {
  this: BaseBuilder[A] =>
  def withSauce(sauce: String): A = _copy("sauce" -> sauce)
}

Do you have suggestions how reflection can be avoided in this scenario yet keeping the builder immutable and also allowing to compose the builder of tiny other builders.

like image 641
Zilvinas Avatar asked Aug 31 '26 03:08

Zilvinas


1 Answers

Smallest change would be to pass the constructor (as a function) instead of a ClassTag:

abstract class BaseBuilder[A <: BaseBuilder[A]](params: BuilderParams)(constructor: BuilderParams => A) {

  protected def _copy(tuples: (String, Object)*): A = constructor(params ++ tuples)

  def build(): Map[String, Object] = {
    params
  }
}

// or class PizzaBuilder(params: BuilderParams = Map.empty) extends BaseBuilder[PizzaBuilder](params)(new PizzaBuilder(_))
case class PizzaBuilder(params: BuilderParams = Map.empty) extends BaseBuilder[PizzaBuilder](params)(PizzaBuilder)
  with CheeseBuilder[PizzaBuilder]
  with SauceBuilder[PizzaBuilder] {
}
like image 162
Alexey Romanov Avatar answered Sep 02 '26 18:09

Alexey Romanov