Идиоматический способ Scala для работы с именами базовых и производных классов? - PullRequest
9 голосов
/ 29 июня 2011

Рассмотрим следующие базовые и производные классы в Scala:

    abstract class Base( val x : String )

    final class Derived( x : String ) extends Base( "Base's " + x )
    {
        override def toString = x
    }

Здесь идентификатор 'x' параметра класса Derived переопределяет поле базового класса, поэтому вызывается toString следующим образом:

    println( new Derived( "string" ).toString )

возвращает значение Derived и дает результат "string".

Таким образом, ссылка на параметр 'x' побуждает компилятор автоматически генерировать поле в Derived, которое подается ввызов toString.Обычно это очень удобно, но приводит к репликации поля (сейчас я сохраняю поля как на базе, так и на основе производных), что может быть нежелательно.Чтобы избежать этой репликации, я могу переименовать параметр класса Derived из «x» во что-то другое, например «_x»:

    abstract class Base( val x : String )

    final class Derived( _x : String ) extends Base( "Base's " + _x )
    {
        override def toString = x
    }

Теперь вызов toString возвращает «строку базы», ​​чего я и хочу,К сожалению, код теперь выглядит несколько уродливо, и использование именованных параметров для инициализации класса также становится менее изящным:

    new Derived( _x = "string" ) 

Существует также риск того, что вы забудете присвоить параметрам инициализации производных классов разные имена и непреднамеренноссылка на неправильное поле (нежелательно, так как базовый класс может фактически иметь другое значение).

Есть ли лучший способ?

Изменить 1 : Чтобы уточнить, ядействительно нужны только базовые значения;Производные просто необходимы для инициализации полей базового класса.Пример ссылается только на них, чтобы проиллюстрировать возникающие проблемы.

Редактировать 2 : На самом деле, этот пример был бы более понятным, если бы я использовал vars вместо val, поскольку это подчеркивает проблему со значениями, которые впоследствии изменяются в базовом классе:

    class Base( var x : Int ) { def increment() { x = x + 1 } }
    class Derived( x : Int ) extends Base( x ) { override def toString = x.toString }

    val derived = new Derived( 1 )
    println( derived.toString )     // yields '1', as expected
    derived.increment()
    println( derived.toString )     // still '1', probably unexpected

Редактировать 3 : Было бы неплохо иметь способ подавить автоматическую генерацию поля, если производный класс в противном случае скрыл бы поле базового класса.Может показаться, что компилятор Scala действительно мог бы быть разработан для вас, но, конечно, это противоречит более общему правилу «приближенных» идентификаторов (производный класс «x»), скрывающих более удаленные (базовый класс).'Икс').Кажется, что довольно хорошим решением будет модификатор типа 'noval', например, так:

    class Base( var x : Int ) { def increment() { x = x + 1 } }
    class Derived( noval x : Int ) extends Base( x ) { override def toString = x.toString }

    val derived = new Derived( 1 )
    println( derived.toString )     // yields '1', as expected
    derived.increment()
    println( derived.toString )     // still '2', as expected

Ответы [ 7 ]

6 голосов
/ 29 июня 2011

Поскольку базовый класс абстрактный, он не выглядит так, как если бы вы действительно хотели val (или var) в классе Base со связанным полем поддержки. Вместо этого вы просто хотите гарантировать, что такая вещь будет доступна в конкретных подклассах.

В Java вы бы использовали метод доступа, такой как getX, для достижения этой цели.

В Scala мы можем пойти лучше, vals, vars и defs занимают одно и то же пространство имен, поэтому val можно использовать для реализации абстрактного def (или для переопределения конкретного def, если это то плывет твоя лодка). Более формально это известно как «Принцип единообразного доступа»

abstract class Base{ def x: String }

class Derived(val x: String) extends Base {
    override def toString = x
}

Если вам нужно, чтобы x можно было установить с помощью ссылки на Base, вам также необходимо объявить «установщик» (который затем может быть реализован с помощью var):

abstract class Base {
  def x: String
  def x_=(s: String): Unit
}

class Derived(var x: String) extends Base {
    override def toString = x
}

(не то, чтобы я когда-либо поощрял изменчивость в любом проекте; если только для этого не было особенно убедительного обоснования. Существует слишком много веских причин для предпочтения неизменности по умолчанию)

UPDATE

Преимущества этого подхода:

  • x может быть полностью синтетическим значением, полностью реализованным в терминах других значений (то есть площадь круга, для которого вы уже знаете радиус)
  • x может быть реализовано на любой произвольной глубине в иерархии типов и не требует быть явно пропущенным через каждый промежуточный конструктор (каждый раз с другим именем)
  • Требуется только одно вспомогательное поле, поэтому память не теряется.
  • Поскольку теперь он не нуждается в конструкторе, Base может быть реализован как черта; если ты так хочешь
6 голосов
/ 29 июня 2011

Идиоматическим способом избежать дублирования поля было бы написать

abstract class Base { val x: String }

final class Derived(val x: String) extends Base {
   def toString = x
}

Однако в вашей версии выглядит, что вы действительно хотите второе поле, поскольку у вас есть два различных значения.Как вы правильно заметили, присвоение этим полям одного и того же имени может привести к путанице.

Поскольку на самом деле вам не нужен аргумент конструктора вне конструктора, вы можете использовать этот подход (частный конструктор ссопутствующий модуль, который действует как фабрика):

abstract class Base { val x: String }

final class Derived private (val x: String) extends Base {
   def toString = x
}
object Derived {
   def apply(x: String) = new Derived("Base " + x)
}
1 голос
/ 04 июля 2011

По предложению @jsuereth я создал расширение, отмеченное галочкой для Scala, просто для протокола, которое, я надеюсь, правильно подытоживает содержание обсуждений здесь. Спасибо за ваш вклад! Билет можно найти здесь, содержание ниже: https://issues.scala -lang.org / browse / SI-4762

Непреднамеренное затенение полей базового класса в производных классах, желательно предупреждение

Проблема возникает, когда (a) параметр класса в производном классе использует тот же символ как поле или функция в базовом классе и (б) этот символ базового класса впоследствии доступен в производном классе. Параметр производного класса заставляет компилятор автоматически генерировать поле с тем же именем, что и тени символ базового класса. Любая ссылка на этот символ в производном классе, предназначен для ссылки на поле базового класса, то непреднамеренно (а) вызывает дублирующее определение поля и (б) неожиданно (но правильно) относится к автоматически сгенерированному полю в производном классе.

Пример кода:

class Base( val x : String )
class Derived( x : String ) extends Base( x ) { override def toString = x }

Поскольку базовый класс имеет значение val для генерации поля и производного класса нет, разработчик явно намерен использовать «Производные» «х» только для сквозной к Base, и поэтому ожидает ссылку в 'toString' на дать базовое значение. Вместо этого ссылка в 'toString' вызывает компилятор автоматически генерировать поле Derived.x, которое скрывает поле Base.x, что привело к ошибке. Компилятор ведет себя правильно, но в результате ошибка программирования.

Сценарий использования (с полями var для более наглядной иллюстрации рисков):

class Base( var x : Int ) { def increment() { x = x + 1 } }
class Derived( x : Int ) extends Base( x ) { override def toString = x.toString }

val derived = new Derived( 1 )
println( derived.toString )     // yields '1', as expected
derived.increment()
println( derived.toString )     // still '1', probably unexpected

Поскольку эта проблема возникает всякий раз, когда кто-либо использует способ инициализации по умолчанию Поля базового класса из производного класса в Scala, сценарий быть чрезвычайно распространенным явлением и приводить к множеству ошибок программирования (легко исправляемых, но все же) для новых пользователей.

Существует легкое решение этой проблемы (используйте разные имена для производных Параметр класса, например, _x, theX, initialX и т. д.), но это вводит ненужные лишние символы.

Решение A (минимальное): выдает предупреждение всякий раз, когда компилятор делает вывод, что Параметр класса требует автоматически сгенерированного поля в производном классе, который затеняет символ, уже определенный в базовом классе.

Решение B: Обход, все еще необходимый для решения A, должен прийти с новым именем символа каждый раз, когда инициализируется поле базового класса. это сценарий возникает все время и загрязнение пространства имен с обходным путем Имена полей, такие как _x и theX, кажутся нежелательными. Вместо этого это может быть хорошо иметь способ подавить автоматическую генерацию поля, если разработчик определяет, что символы производного класса в противном случае будут скрывать базу символ класса (например, после предупреждения решения A). Может быть полезным дополнение к Scala будет модификатор типа «noval» (или «passthrough» или 'temp' или что-то еще - в дополнение к 'val' и 'var'), например:

class Base( var x : Int ) { def increment() { x = x + 1 } }
class Derived( noval x : Int ) extends Base( x ) { override def toString = x.toString }

val derived = new Derived( 1 )
println( derived.toString )     // yields '1', as expected
derived.increment()
println( derived.toString )     // still '2', as expected
1 голос
/ 29 июня 2011

вы уже предоставили ответ, который работает

abstract class Base( val x : String )

final class Derived( _x : String ) extends Base( "Base's " + _x )
{
    override def toString = x
}

Если проблема в том, что _x не является подходящим именем, вам следует использовать осмысленное.В качестве альтернативы, вы можете объявить ваши классы следующим образом:

abstract class Base( val _x : String )

final class Derived( x : String ) extends Base( "Base's " + x )
{
    override def toString = _x
}

И теперь у вас будет «хороший» синтаксис для инициализации Derived экземпляров.

Если scala должен был разрешить

способ подавления автоматической генерации поля, если производный класс в противном случае скрыл бы поле базового класса.

Мне кажется, это очень низкоуровневая деталь, которую вы не делаетехочу разобраться в коде.Если это можно сделать безопасно, компилятор должен сделать это за вас.

1 голос
/ 29 июня 2011

Вы можете попробовать это:

abstract class Base( val x : String )

final class Derived( _x : String ) extends Base( _x ) {
  override val x  = "Base's " + _x
  override def toString = x
}

Тогда

println(new Derived("string").toString)

печатает именно то, что вы хотите

0 голосов
/ 28 февраля 2013

@ Грегор Шейдт Ваш код не будет работать, если я переместу toString () вниз к Derived, как показано ниже:

object Test {
  abstract class Base ( val x: String)

  final class Derived(x: String) extends Base(x + " base") {
    override def toString() = x
  }

  def main(args: Array[String]): Unit = {
    val d  = new Derived( "hello")
    println( d) // hello
  }
}

В сообщении с официального сайта написано ,

Параметр, такой как класс Foo (x: Int), превращается в поле, если на него ссылается один или несколько методов

И ответ Мартина подтверждает его истинность:

Это все правда, но это следует рассматривать как метод реализации.Вот почему в спецификации об этом ничего не говорится.

, поскольку нет способа предотвратить действие компилятора, я могу выбрать более надежный способ ссылки на поле базового класса - использовать другое имя дляПример использования подчеркивания "_" в качестве префикса, как показано ниже:

object Test {
  abstract class Base ( val x: String)

  final class Derived(_x: String) extends Base(_x + " base") {
    override def toString() = x
  }

  def main(args: Array[String]): Unit = {
    val d  = new Derived( "hello")
    println( d) // hello
  }
}
0 голосов
/ 29 июня 2011

Хорошо, прежде всего я хотел бы отметить, что ответ @Yuriy Zubarev, вероятно, является тем, что вы действительно хотите получить. Во-вторых, я думаю, что проблема может заключаться в вашем дизайне. Проверь это. Это часть вашего кода:

extends Base( "Base's " + _x )

Таким образом, некоторое значение x входит в ваш производный класс и модифицируется информацией (в данном случае "Base's " + ...). Ты видишь проблему? Почему ваш производный тип знает то, что должен знать ваш базовый тип? Вот решение, которое я предлагаю.

abstract class Base {
    // this works especially well if you have a var
    // which is what you wanna have as you pointed out later.
    var x: String
    x = "Base's " + x
}

final class Derived(override var x: String ) extends Base{
   override def toString = x
}

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

...