My but it's sad that Java/Scala has to resort to a design pattern for this (if I'm understanding the post correctly).
In Python, we have keyword arguments and default argument values, which allows for very rich parameter declarations.
Well, first of all, a builder pattern is targetted at complex objects. Imagine, for instance, a maze with multiple rooms and passages, where the creation process might entail adding hundred of such components. Not exactly the sort of thing you want to pass as a single parameter list, I'd imagine. In fact, it might not even be possible to do it, because you might not have all information needed in the same place and/or time.
But, most importantly, "keyword arguments" do not enforce type safety. So even if it were an alternative to any problem requiring the Builder pattern, it would fail at the basic premise of the article, which is providing type safety.
So, I decided to show something you can't do with keyword arguments, which, by the way, are called "named parameters" in Scala 2.8, which is mutual exclusion. I'm adapting this example from here, which was written in reply to a follow up by Jim on his original article.
For this example, I assume a "Car" builder, where one has to make a choice about number of doors and brand, and may also choose to get a convertible, but the convertible is incompatible with cars with five doors. This code is nowhere as clear as Jim's, but gets the job done faster, so I could get on with my life. :-)
So, I hope you like it. And if you can clean it up, or come up with more likely scenarios, please share it.
// This code is based on an idea of Rafael de F. Ferreira and was implemented as a response to
// Jim McBeath's blog post http://jim-mcbeath.blogspot.com/2009/09/type-safe-builder-in-scala-part-2.html
// Everyone is free to use, modify, republish, sell or give away this work without prior consent from anybody.
object Scotch {
sealed abstract class Preparation
case object Neat extends Preparation
case object OnTheRocks extends Preparation
case object WithWater extends Preparation
case class OrderOfScotch private[Scotch](val brand:String, val mode:Preparation, val isDouble:Boolean)
trait Option[+X]
case class Some[+X](x:X) extends Option[X]{
def get:X = x
}
trait None extends Option[Nothing] // this differs in the original implementation
case object None extends None
case class Builder[HAS_BRAND<:Option[String],HAS_MODE<:Option[Preparation],HAS_DOUBLE<:Option[Boolean]] private[Scotch]
(brand:HAS_BRAND
,mode:HAS_MODE
,isDouble:HAS_DOUBLE
) {
def ~[X](f:Builder[HAS_BRAND,HAS_MODE,HAS_DOUBLE] => X):X = f(this)
}
def withBrand[M<:Option[Preparation],D<:Option[Boolean]](brand:String)(b:Builder[None,M,D]):Builder[Some[String],M,D] =
Builder(Some(brand),b.mode,b.isDouble)
def withMode[B<:Option[String],D<:Option[Boolean]](mode:Preparation)(b:Builder[B,None,D]):Builder[B,Some[Preparation],D] =
Builder(b.brand,Some(mode),b.isDouble)
def isDouble[B<:Option[String],M<:Option[Preparation]](isDouble:Boolean)(b:Builder[B,M,None]):Builder[B,M,Some[Boolean]] =
Builder(b.brand,b.mode,Some(isDouble))
def build(b:Builder[Some[String],Some[Preparation],Some[Boolean]]):OrderOfScotch =
OrderOfScotch(b.brand.get,b.mode.get,b.isDouble.get)
def builder:Builder[None,None,None] = Builder(None,None,None)
def test {
val x:OrderOfScotch = builder ~ isDouble(true) ~ withMode(Neat) ~ withBrand("Blubber") ~ build
// builder ~ isDouble(true) ~ withMode(Neat) ~ build // fails
// builder ~ isDouble(true) ~ withMode(Neat) ~ withBrand("Blubber") ~ withBrand("Blubber") ~ build // fails
x
}
}
No comments:
Post a Comment