Scala: ссылка на сопутствующий объект из дочернего класса

Я думаю о следующем макете класса Scala. У меня есть базовая черта, которая представляет собой Item - интерфейс того, что должно быть неизменяемым объектом, который мы можем запрашивать для имени, веса и делать некоторые специфичные для объекта вещи, вызывая такие методы, как equip:

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

Я могу создавать Item реализации, создавая классы case вручную, но я бы хотел иметь какие-то элементы на основе "словаря", то есть статическая карта содержит сопоставление от идентификатора типа к значениям, методы name и 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) для ссылки на объект предмета для "короткого меча". Однако тот же базовый принцип применяется к любым другим типам элементов, таким как Armor, который использует точно такую ​​же реализацию name и 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) из родительского класса - т.е. что мне следует использовать вместо ???.NameDict(typ) и как мне объяснить компилятору, что детский объект-компаньон должен включать эти словари? Есть ли лучший, более похожий на Scala подход к такой проблеме?


person GreyCat    schedule 01.12.2012    source источник
comment
Возможно, вы хотите задать этот связанный вопрос stackoverflow.com/questions/12179092/   -  person Kane    schedule 01.12.2012
comment
Спасибо, Кейн! Это связано, да.   -  person GreyCat    schedule 04.12.2012


Ответы (3)


Вместо того, чтобы искать имена и веса элементов на картах, вы можете использовать экземпляры ваших классов case. На самом деле есть несколько веских причин, по которым вы можете захотеть воспользоваться этим подходом:

  1. Избегайте сложности. Это достаточно просто, возможно, вам не нужно было задавать этот вопрос о том, как наследование работает с сопутствующими классами.
  2. Избегайте повторения. Используя классы case, вам нужно будет определить элементы только один раз.
  3. Избегайте ошибок. При использовании синтетических ключей вам потребуется использовать правильный ключ как в словарях имен, так и в словарях веса. См. Пункт 2.
  4. Написание идиоматического кода. Scala одновременно объектно-ориентирована и функциональна. В этом случае ориентируйтесь на предмет.
  5. Избегайте накладных расходов: получите все свойства элемента за один поиск.

Вот другой взгляд, в котором Предмет играет главную роль:

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 является своего рода анахронизмом. Когда вы определяете val в Scala, он уже неизменен: Почему `private val` и` private final val` разные?

person yakshaver    schedule 01.12.2012
comment
Спасибо за полный и лаконичный ответ - видите ли, я переборщил с картами! - person GreyCat; 04.12.2012
comment
Когда вы что-то перечисляете (например, виды оружия), вы должны кодировать это как перечисление (в Scala некоторые предпочитают case-объекты). Когда вы поймете, что халбарда должна быть алебардой, вам придется произвести глобальную замену (см. Пункт 2). Кроме того, обычно в трейте используется def; доступ всегда через аксессор, так что это то же самое. (Если позже вам понадобятся интересные примеси, использование vals в трейтах проблематично. Вы можете переопределить def с помощью val.) - person som-snytt; 04.12.2012
comment
Вы правы, в любом случае оценка одинакова, поскольку классы case переопределяют свойства как vals. Я изменил ответ, чтобы отразить это. Возможно, будет более подходящим использовать объекты case. - person yakshaver; 04.12.2012

som-snytt прав - просто сделайте так, чтобы объекты Armor и Weapon расширяли черту, скажем, Dictionary, которая имеет элементы NameDict и WeightDict, а затем имеет 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. Или, если вам нужно ссылаться на них только через их индекс, вы можете определить элементы непосредственно на карте.

person Luigi Plinge    schedule 01.12.2012

Я думаю, ты хочешь object Weapon extends Dictionary. Однажды я назвал его Оружейной палатой, имея в виду оружейный завод. Но подойдет любой -ory или -ary. Я бы также изменил интегральный тип на Enumeration. Затем определите Weapon.apply (kind: Kind), чтобы вернуть меч, когда вы используете Weapon (Kinds.Sword).

Этот шаблон фабричных методов для признаков, которые должны быть реализованы сопутствующими объектами, используется структурой коллекций, которая носит образовательный характер.

person som-snytt    schedule 01.12.2012
comment
Спасибо, что показали метод объекта extends - я действительно рад, что он работает :) - person GreyCat; 04.12.2012