Factory design pattern in Scala with case classes
I am trying to implement a factory design pattern in Scala using the apply methods available on a companion object. I have the following approach.
sealed trait MyType { def param: String } case class TypeA(param: String) extends MyType case class TypeB(param: String, anotherParam: String) extends MyType object MyType { def apply(param: String): TypeA = ??? def apply(param, anotherParam: String): TypeB = ??? }
How do I now force the callers of the above function to pass through the companion object when instantiated TypeA
or TypeB
?
source to share
You can move case classes inside the companion object and set constructors as private and only access them inside the companion object.
sealed trait MyType {
def param: String
}
object MyType {
case class TypeA private[MyType] (param: String) extends MyType
case class TypeB private[MyType] (param: String, anotherParam: String) extends MyType
def apply(param: String): TypeA = TypeA(param)
def apply(param: String, anotherParam: String): TypeB = TypeB(param, anotherParam)
}
No one will be able to instantiate classes directly, unless it's reflection.
scala> MyType("Test")
res0: MyType.TypeA = TypeA(Test)
scala> MyType("Test", "another test")
res1: MyType.TypeB = TypeB(Test,another test)
scala> MyType.TypeA("test??")
<console>:12: error: constructor TypeA in class TypeA cannot be accessed in object $iw
MyType.TypeA("test??")
^
source to share
You can just call the method apply
for the case classes. There appears to be no way for the client code to not call directly TypeA.apply
, as that would prevent it from being called MyType
.
sealed trait MyType {
def param: String
}
case class TypeA(param: String) extends MyType
case class TypeB(param: String, anotherParam: String) extends MyType
object MyType {
def apply(param: String): TypeA = TypeA(param)
def apply(param: String, anotherParam: String): TypeB = TypeB(param, anotherParam)
}
source to share
The sign is MyType
sealed. That others can do something like new MyType{}
to create it.
Then you can remove the case classes.
// No more public case classes TypeA & TypeB object MyType { def apply(p: String): MyType = /* case A */ new MyType { val param = p } private case class InternalB(param: String, other: String) extends MyType def apply(param: String, anotherParam: String): MyType = InternalB(param, anotherParam) }
At this point, you MyType
need to use a companion object to create instances .
Then you can reconnect the patterns for these different cases.
object MyType { // the apply functions, plus extractors thereafter... /** Extracts mandatory parameter whatever is the case. */ def unapply(t: MyType): Option[String] = Some(t.param) /** Extracts both parameter, extra parameter for case B, None for other */ def unapply(t: MyType): Option[(String, String)] = t match { case InternalB(mandatory, extra)/* Only possible there as private */ => Some(mandatory -> extra) case _ => None } } // Then pattern matching can do... val test1: Boolean = MyType("A") match { case MyType(param) => true case _ => false } // Will be true val test2: Boolean = MyType("B", "extraB") match { case MyType(param, extra) => true case _ => false } // Will be true val test3: Int = MyType("A") match { case MyType(param, extra) => 2 case MyType(param) => 1 case _ => 0 } // Will be 1 val test4: Boolean = MyType("B", "extraB") match { case MyType(param) => true case _ => false } // Will be true
It allows you to completely control the instance and abstract from the implementation of cases.
source to share