Фабричный дизайн шаблона в Scala с кейсом

Я пытаюсь реализовать шаблон проектирования фабрики в Scala, используя методы apply, доступные для объекта-компаньона. У меня есть следующий подход.

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 = ???
}

Как мне теперь заставить вызывающих из вышеупомянутой черты проходить через объект-компаньон при создании экземпляров TypeA или же TypeB?

3 ответа

Решение

Вы можете перемещать классы case внутри объекта-компаньона и устанавливать конструкторы как частные и доступ к ним возможен только внутри объекта-компаньона.

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

Никто не сможет создать экземпляр класса case напрямую, если не будет отражения.

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

Вы можете просто позвонить apply Метод кейсов самих классов. Кажется, нет способа предотвратить вызов клиентского кода TypeA.apply напрямую, хотя, как это помешало бы 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)
}

Черта MyType запечатан. Что мне другие могут сделать что-то вроде new MyType{} чтобы создать его экземпляр.

Затем вы можете удалить классы дела.

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

На данный момент, необходимо использовать объект-компаньон для создания MyType экземпляров.

Затем вы можете восстановить сопоставление с образцом для этих разных случаев.

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

Это позволяет полный контроль над реализацией и абстракцией над реализацией дел.

Другие вопросы по тегам