不可变类层次结构中的多态更新

时间:2012-01-10 10:31:58

标签: scala inheritance immutability

我希望能够根据具体类可能具有的各种属性来组合特征中的域对象。当我的对象是可变的时,这非常简单。例如:

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 个答案:

答案 0 :(得分:1)

你可能会因添加例如为您的traits抽象def withHitPoints(points:Int)方法,它返回具有不同属性值的容器对象的副本。这减少了使用情况:

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

但是否则每个具体类别的每个属性都需要一个额外的方法,所以我不确定它是否真的解决了你的问题。对于像Scala这样的静态语言来说,这感觉不合适(我也不会为这个特定用例的不可变性而烦恼);这里的不可变解决方案可能是动态语言的更好候选者。

答案 1 :(得分:1)

您希望避免M具体类中的N更新方法。 无论多么痛苦,我认为这是不可能的。您将需要访问复制方法或至少每个具体类的构造函数。它们都不能被抽象出来,如下所述:Case class copy() method abstraction 因此,最终你将总是得到N x M'样板'代码。

答案 2 :(得分:0)

对于死灵法术表示抱歉,但值得指出的是,使用F-bounded多态性是可行的:

  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