【问题标题】:Polymorphic updates in an immutable class hierarchy不可变类层次结构中的多态更新
【发布时间】:2012-01-10 10:31:57
【问题描述】:

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

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。如果我有很多具体的类,以及我可能想要混合的很多不同的属性,那么避免样板代码爆炸的最佳方案是什么?

【问题讨论】:

    标签: scala inheritance immutability


    【解决方案1】:

    你可能有一些运气添加例如将 def withHitPoints(points: Int) 方法抽象为您的特征,该方法返回具有不同属性值的容器对象的副本。这将使用量减少到类似:

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

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

    【讨论】:

    • 是的,虽然我希望避免在 M 个具体类中的 N 个更新方法的混乱。正如您所指出的,在动态语言中它会非常简单。这个用例显然非常简化,但我认为当您将更新与不可变继承层次结构混合时,这个问题经常会出现。
    • 我不会争辩说这是 Scala 不能很好处理的事情,但根据我的经验,这是一个不会经常出现的场景——除非你当然在这个时候死心塌地建模。
    • 可能,虽然我想知道 Sapir-Whorf 在多大程度上是这样的:一种语言不能很好处理的场景变成了不经常出现的场景......
    • @TomerGabel 很奇怪,因为我在我参与的第一个大型 Scala 程序中立即遇到了这种情况。基于继承层次结构的多态性是构建复杂系统中最重要的概念之一,这种限制只是切断了它的一条腿,因为您只能在不进行任何数据更新的查询方法上使用它。您最终要么满足于普通的旧可变数据类型,要么摆弄类型成员、泛型和伴随对象以尝试实现相同的目标。
    【解决方案2】:

    您希望避免在 M 个具体类中使用 N 个更新方法。 不管多么痛苦,我认为这是不可能的。您将需要访问复制方法或至少每个具体类的构造函数。它们中的任何一个都不能被抽象出来,正如在 Case class copy() method abstraction 中所讨论的那样 所以最后你总是会得到 N x M '样板'代码。

    【讨论】:

      【解决方案3】:

      对死灵表示抱歉,但值得指出的是,使用 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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多