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

?

+3


source to share


3 answers


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??")
                     ^

      

+10


source


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

      

+1


source


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.

0


source







All Articles