Scala: как определить метод, который возвращает экземпляр подкласса - PullRequest
2 голосов
/ 04 ноября 2019

В проекте, над которым я работаю, есть некоторый код, который по сути выглядит следующим образом:

sealed trait Character {
  def tags: Seq[String]
  def life: Int
  // other defs
}
object Character {
  def addTag[T <: Character](character: T, tag: String): T = {
    val newTags = character.tags :+ tag
//    character.copy(tags = newTags)  // this doesn't compile

    character match {
      case c: Person => c.copy(tags = newTags).asInstanceOf[T]
      case c: Beast => c.copy(tags = newTags).asInstanceOf[T]
      // ten more cases to match each subclass
      ......
      case _ => character
    }
  }
}

case class Person(title: String,
                  firstName: String,
                  lastName: String,
                  tags: Seq[String],
                  life: Int,
                  weapon: String
                 ) extends Character
case class Beast(name: String,
                 tags: Seq[String],
                 life: Int,
                 weight: Int
                ) extends Character
// ten other case classes that extends Character
......

Код работает, но метод addTag выглядит не очень красиво по двум причинам: во-первых, он использует asInstanceOf;во-вторых, в нем много строк case c: ......, каждая из которых почти одинакова.

Есть ли способ улучшить код?

1 Ответ

2 голосов
/ 04 ноября 2019

Поскольку метод copy специфичен для каждого класса дел (принимает разные параметры), его нельзя использовать из суперкласса. Что вы можете сделать:


  sealed trait Character {
    def tags: Seq[String]

    def life: Int

    // other defs
  }

  trait Taggable[T <: Character] {
    def addTags(t: T, newTags: Seq[String]): T
  }

  object Character {
    def addTag[T <: Character: Taggable](character: T, tag: String): T = {
      val newTags = character.tags :+ tag
      implicitly[Taggable[T]].addTags(character, newTags)
    }
  }

  case class Person(title: String,
                    firstName: String,
                    lastName: String,
                    tags: Seq[String],
                    life: Int,
                    weapon: String
                   ) extends Character

  object Person {
    implicit val taggable: Taggable[Person] = new Taggable[Person] {
      override def addTags(t: Person, newTags: Seq[String]): Person = t.copy(tags = newTags)
    }
  }

  case class Beast(name: String,
                   tags: Seq[String],
                   life: Int,
                   weight: Int
                  ) extends Character

  Character.addTag(Person("", "", "", Seq(), 1, ""), "")
//  Character.addTag(Beast("", Seq(), 1, 1) // needs implicit as well

При этом используется класс типов Taggable, который должен быть реализован каждым подклассом.

...