Полиморфные обновления в неизменной иерархии классов - PullRequest
19 голосов
/ 10 января 2012

Я хотел бы иметь возможность собирать доменные объекты из признаков в соответствии с различными свойствами, которые могут иметь конкретные классы. Когда мои объекты изменчивы, это довольно просто. Например:

trait HasHitPoints { var hitPoints: Int = 100 }
trait HasBearing { var bearing: Double = 0 }

class Ship extends HasHitPoints with HasBearing
class Base extends HasHitPoints

val entities = new Ship :: new Base :: Nil
entities.collect { case h: HasHitPoints => h.hitPoints += 10 }

В частности, я могу полиморфно читать или обновлять любой экземпляр HasHitPoints, не зная конкретного типа.

Каков наилучший способ реализовать это с неизменяемыми объектами? Если я счастлив просто прочитать свойства, я мог бы сделать что-то вроде:

trait HasHitPoints { val hitPoints: Int }
trait HasBearing { val bearing: Double }

case class Ship(hitPoints: Int, bearing: Double) extends HasHitPoints with HasBearing
case class Base(hitPoints: Int) extends HasHitPoints

val things = Ship(50, 0) :: Base(100) :: Nil

val totalHitPoints = things.collect { case h: HasHitPoints => h.hitPoints }.sum

Кроме того, я могу легко изменить конкретные классы, используя copy, если знаю точный тип. Сложной частью является обновление произвольного HasHitPoints, например. Если у меня есть много конкретных классов и много разных свойств, которые я бы хотел добавить, какова лучшая схема, чтобы избежать взрыва шаблонного кода?

Ответы [ 3 ]

1 голос
/ 10 января 2012

Вы надеетесь избежать N методов обновления в M конкретных классах. Как бы ни было больно, я думаю, что это просто невозможно. Вам потребуется доступ к методу копирования или, по крайней мере, к конструктору каждого конкретного класса. Ни один из них не может быть абстрагирован, как это также обсуждается в: абстракция метода copy () класса Case Таким образом, в итоге вы всегда получите кодовый шаблон N x M.

1 голос
/ 10 января 2012

Вам может повезти с добавлением, например, абстрактного метода def withHitPoints (points: Int) к вашим чертам, который возвращает копию объекта контейнера с другим значением свойства.Это уменьшает использование до чего-то вроде:

val damagedActors = actors map { actor => actor.withHitPoints( actor.hitPoints - 10 ) }

Но в противном случае потребуется дополнительный метод для каждого свойства конкретного класса, поэтому я не уверен, что это действительно решит вашу проблему.Это не подходит для статического языка, такого как Scala (и я бы не стал беспокоиться об неизменности для этого конкретного варианта использования);неизменное решение здесь может быть лучшим кандидатом для динамического языка.

0 голосов
/ 03 апреля 2018

Извините за некромантию, но стоит указать, что это выполнимо, используя F-ограниченный полиморфизм:

  trait HasHitPoints[Self <: HasHitPoints[Self]] { 
    val hitPoints: Int 
    def updateHitpoints(f: Self => Int): Self
  }
  trait HasBearing { val bearing: Double }

  case class Ship(hitPoints: Int, bearing: Double)
      extends HasHitPoints[Ship]
      with HasBearing {
    override def updateHitpoints(f: Ship => Int): Ship = copy(hitPoints = f(this))
  }
  case class Base(hitPoints: Int) extends HasHitPoints[Base] {
    override def updateHitpoints(f: Base => Int): Base = copy(hitPoints = f(this))
  }

  val things = Ship(50, 0) :: Base(100) :: Nil

  val heal = things.map(_.updateHitpoints(_.hitPoints + 10))

  val totalHitPoints = heal.map(_.hitPoints).sum
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...