【问题标题】:Scala: referencing companion object from a child classScala:从子类引用伴随对象
【发布时间】:2012-12-01 00:39:32
【问题描述】:

我正在考虑以下 Scala 类布局。我有一个代表项目的基本特征 - 一个应该是不可变对象的接口,我们可以通过调用 equip 之类的方法来查询名称、重量和做一些特定于对象的事情:

trait Item {
  def name: String
  def weight: Int
  def equip // ... more abstract methods
}

我可以创建 Item 实现,手动创建案例类,但我想要某种基于“字典”的项目 - 即静态映射包含从类型 ID 到值的映射,nameweight 方法只需使用存储的类型 ID 查询字典:

object Weapon {
  final val NameDict = Map(1 -> "short sword", 2 -> "long sword")
  final val WeightDict = Map(1 -> 15, 2 -> 30)
}
case class Weapon(typ: Int) extends Item {
  import Weapon._
  def name = NameDict(typ)
  def weight = WeightDict(typ)
  def equip = ... // implementation for weapon
}

到目前为止,一切都很好。我可以使用Weapon(1) 来引用“短剑”的项目对象。但是,相同的基本原理适用于任何其他项目类型,例如Armor,它使用完全相同的nameweight 实现,但完全不同的equip 和其他抽象方法实现,例如:

object Armor {
  final val NameDict = Map(3 -> "bronze armor", 4 -> "iron armor")
  final val WeightDict = Map(3 -> 100, 4 -> 200)
}
case class Armor(typ: Int) extends Item {
  import Armor._
  def name = NameDict(typ)
  def weight = WeightDict(typ)
  def equip = ... // implementation for armor
}

看起来很像Weapon,不是吗?我想以类似的方式分解出常见的模式(即在伴随对象字典和常见的typ 值中实现查找):

abstract class DictionaryItem(typ: Int) extends Item {
  def name = ???.NameDict(typ)
  def weight = ???.WeightDict(typ)
}

object Weapon {
  final val NameDict = Map(1 -> "sword", 2 -> "bow")
  final val WeightDict = Map(1 -> 15, 2 -> 30)
}
case class Weapon(typ: Int) extends DictionaryItem(typ) {
  def equip = ... // implementation for weapon
}

但是如何从父类中引用子类的伴随对象(如Weapon.NameDict)——即我应该使用什么来代替???.NameDict(typ),以及如何向编译器解释子类的伴随对象必须包含这些字典?有没有更好、更 Scala 风格的方法来解决此类问题?

【问题讨论】:

标签: class scala case-class companion-object


【解决方案1】:

您可以使用案例类的实例,而不是在地图中查找项目的名称和权重。事实上,您可能希望采用这种方法有一些很好的理由:

  1. 避免复杂性:很简单,您可能不需要发布此内容 关于继承如何与伴生类一起工作的问题。
  2. 避免重复:使用案例类,您只需 定义您的项目一次。
  3. 避免错误:使用合成密钥,您需要 在名称和重量字典中使用正确的键。看 第 2 项。
  4. 编写惯用代码:Scala 既是面向对象的又是函数式的。在 在这种情况下,请围绕项目定位自己。
  5. 避免开销:通过一次查找获取所有项目的属性。

这里是 Item 扮演主角的不同看法:

package object items {
  val weapons = Weapon("halbred", 25) :: Weapon("pike", 35) :: Nil
  val armor = Armor("plate", 65) :: Armor("chainmail", 40) :: Nil
  val dictionary = (weapons ::: armor) map(item => item.name -> item) toMap
}

请注意,名称和权重打包在实例中,而不是打包为映射中的值。并且没有要管理的索引。但是,如果您出于某种原因需要索引,则可以轻松地使用该项目在设备列表中的位置或将属性添加到 Item trait。

而且,你可以这样使用它:

> items dictionary("halbred")        // Weapon(halbred, 25)
> items dictionary("plate") weight   // 65

最后一点,Scala 单词中的 final 关键字有点不合时宜。在 Scala 中定义 val 时,它已经是不可变的:Why are `private val` and `private final val` different?

【讨论】:

  • 感谢您提供完整而简洁的回答 - 你看是对的,我用地图过度设计了这些东西!
  • 当你枚举某些东西(比如各种武器)时,你应该将它编码为枚举(在 Scala 中,有些人更喜欢 case 对象)。当您意识到 halbred 应该是 halberd 时,您将不得不全局替换(参见第 2 项)。此外,通常在特征中使用 def ;访问总是通过访问器,所以它是一样的。 (如果你以后想要有趣的混合,在特征中使用 vals 是有问题的。你可以用 val 覆盖 def。)
  • 你是对的,无论哪种情况,评估都是相同的,因为案例类将属性覆盖为 vals。我已经修改了答案以反映这一点。使用案例对象可能更合适。
【解决方案2】:

som-snytt 是对的 - 只需让 ArmorWeapon 对象扩展一个特征,例如,Dictionary 具有 NameDictWeightDict 成员,然后具有 DictionaryItem(typ: Dictionary)

不过,这似乎是一种相当复杂的做事方式,如果您按数字查找所有属性,则很容易出错且难以重构。更自然的方法是将实际对象放入地图中,例如:

case class Weapon(name: String, weight: Int, equip: ...) extends Item
case class Armor(name: String, weight: Int, equip: ...) extends Item

val ShortSword = Weapon(name = "short sword",
                        weight = 15,
                        equip = ...)

val LongSword = Weapon("long sword", 20, ...)

val Weapons = Map(
  1 -> ShortSword,
  2 -> LongSword
)

所以你会写例如Weapons(1).name而不是Weapon.NameDict(1)。或者,您可以只写ShortSword.name。或者,如果您只需要通过它们的索引来引用它们,您可以直接在 Map 中定义这些项目。

【讨论】:

    【解决方案3】:

    我想你想要object Weapon extends Dictionary。我曾经称它为军械库,意思是武器工厂。但是任何 -ory 或 -ary 都可以。我还将积分类型更改为枚举。然后定义 Weapon.apply(kind: Kind) 在你使用 Wea​​pon(Kinds.Sword) 时返回一把剑。

    集合框架使用这种由伴随对象实现的特征上的工厂方法模式,具有教育意义。

    【讨论】:

    • 感谢您展示对象 extends 方法 - 我很高兴看到它工作:)
    猜你喜欢
    • 2018-02-06
    • 1970-01-01
    • 2016-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多