Reputation: 15385
I'm trying to implement a factory design pattern in Scala using the apply methods available on the 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 trait to go via the companion object when creating instances of TypeA
or TypeB
?
Upvotes: 3
Views: 4542
Reputation: 7596
You can move the case classes inside the companion object, and set the constructors to be private and accessed only within 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 would be able to instantiate the case classes directly, unless though 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??")
^
Upvotes: 12
Reputation: 9168
The trait MyType
is sealed. That me others can do something like new MyType{}
to instantiate 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, it's required to use companion object to create MyType
instances.
Then you can restore pattern matching 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 a full control over instantiation, and abstraction over implementation of cases.
Upvotes: 1
Reputation: 55569
You can simply call the apply
method of the case classes themselves. There doesn't seem to be a way to prevent client code from calling TypeA.apply
directly, though, as that would prevent MyType
from calling it.
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)
}
Upvotes: 1