Scala:引用子类中的伴随对象

时间:2012-12-01 00:39:33

标签: class scala case-class companion-object

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

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

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

weight

到目前为止,这么好。我可以使用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),它使用完全相同的Armorname实现,但完全不同weight和其他摘要方法实现,例如:

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?如何向编译器解释子对象的伴随对象必须包含这些词典?对于这样的问题,是否有更好的,更多斯卡拉式的方法?

3 个答案:

答案 0 :(得分:4)

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

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

以下是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
}

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

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

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

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

答案 1 :(得分: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中定义项目。

答案 2 :(得分:1)

我想你想要object Weapon extends Dictionary。我曾经称它为军械库,意为武器工厂。但是任何或者任何人都会这样做。我也会将积分typ更改为Enumeration。然后定义Weapon.apply(kind:Kind)以在你武器(Kinds.Sword)时返回一把剑。

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