Каковы недостатки в объявлении тематических классов Scala? - PullRequest
102 голосов
/ 11 января 2011

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

  • Всенеизменяемый по умолчанию
  • Автоматически определяемые геттеры
  • Достойная реализация toString ()
  • Соответствует equals () и hashCode ()
  • Сопутствующий объект с методом unapply ()для сопоставления

Но каковы недостатки определения неизменяемой структуры данных в качестве класса наблюдения?

Какие ограничения накладываются на класс или его клиентов?

Есть ли ситуации, когда вы предпочитаете не-кейс-класс?

Ответы [ 5 ]

97 голосов
/ 11 января 2011

Сначала хорошие биты:

Все неизменяемые по умолчанию

Да, и даже могут быть переопределены (используя var), если вам это нужно

Автоматически определяемые геттеры

Возможно в любом классе с помощью префикса параметров с val

Приличная toString() реализация

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

Соответствует equals() и hashCode()

В сочетании с простым сопоставлением с образцом, это основная причина, по которой люди используют классы дел

Объект-компаньон с методом unapply() длясопоставление

Также возможно выполнить вручную на любом классе с помощью экстракторов

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


Тогда плохо, есть только несколько реальных ограничений с классами дел:

Вы можетеНе определяйте apply в объекте-компаньоне, используя ту же сигнатуру, что и метод, сгенерированный компилятором

На практике это редко является проблемой.Изменение поведения сгенерированного метода apply гарантированно удивит пользователей, и его следует настоятельно не поощрять. Единственное оправдание для этого - проверка входных параметров - задача, лучше всего выполняемая в основном теле конструктора (которая также делает проверку доступной при использовании * 1057).*)

Вы не можете подкласс

True, хотя все еще возможно для класса case сам по себе быть потомком.Один из распространенных шаблонов заключается в построении иерархии классов признаков с использованием классов дел в качестве конечных узлов дерева.

Также стоит отметить модификатор sealed.Любой подкласс черты с этим модификатором должен быть объявлен в том же файле.При сопоставлении шаблонов с экземплярами признака компилятор может предупредить вас, если вы не проверили все возможные конкретные подклассы.В сочетании с классами case это может обеспечить очень высокий уровень доверия к вашему коду, если он компилируется без предупреждения.

Как подкласс класса Product, классы case не могут иметь более 22 параметров

Нет реального обходного пути, кроме как прекратить злоупотреблять классами с таким количеством параметров:)

Также ...

Еще одно ограничение, которое иногда отмечается, заключается в том, что Scala (в настоящее время) не поддерживает ленивыхпараметры (например, lazy val с, но как параметры).Обходной путь к этому состоит в том, чтобы использовать параметр по имени и назначить его ленивому значению в конструкторе.К сожалению, параметры по именам не сочетаются с сопоставлением с образцом, что не позволяет использовать технику с классами case, поскольку это нарушает сгенерированный компилятором экстрактор.

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

49 голосов
/ 11 января 2011

Один большой недостаток: классы case не могут расширять класс case.Это ограничение.

Другие преимущества, которые вы пропустили, перечислены для полноты: совместимая сериализация / десериализация, нет необходимости использовать ключевое слово «new» для создания.

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

10 голосов
/ 12 января 2011

Я думаю, что здесь применяется принцип TDD: не переусердствуйте. Когда вы объявляете что-то case class, вы объявляете много функций. Это уменьшит гибкость, с которой вы можете изменить класс в будущем.

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

6 голосов
/ 09 июня 2016

Есть ли ситуации, когда вы предпочитаете не-кейс-класс?

Мартин Одерски дает нам хорошую отправную точку в своем курсе Принципы функционального программирования в Scala (Лекция 4.6 - Сопоставление с образцом), которые мы могли бы использовать, когда нам нужно выбрать между классом и классом дела. Глава 7 Scala By Example содержит тот же пример.

Скажем, мы хотим написать интерпретатор для арифметических выражений. к сначала все будет просто, мы ограничимся только числами и + операции. Такие выражения могут быть представлены как класс иерархия, с абстрактным базовым классом Expr в качестве корня и двумя Подклассы Количество и Сумма. Тогда выражение 1 + (3 + 7) будет представлено как

новая сумма (новый номер (1), новая сумма (новый номер (3), новый номер (7)))

abstract class Expr {
  def eval: Int
}

class Number(n: Int) extends Expr {
  def eval: Int = n
}

class Sum(e1: Expr, e2: Expr) extends Expr {
  def eval: Int = e1.eval + e2.eval
}

Кроме того, добавление нового класса Prod не влечет за собой никаких изменений в существующем коде:

class Prod(e1: Expr, e2: Expr) extends Expr {
  def eval: Int = e1.eval * e2.eval
}

Напротив, добавление нового метода требует модификации всех существующих классов.

abstract class Expr { 
  def eval: Int 
  def print
} 

class Number(n: Int) extends Expr { 
  def eval: Int = n 
  def print { Console.print(n) }
}

class Sum(e1: Expr, e2: Expr) extends Expr { 
  def eval: Int = e1.eval + e2.eval
  def print { 
   Console.print("(")
   print(e1)
   Console.print("+")
   print(e2)
   Console.print(")")
  }
}

Та же проблема решена с кейс-классами.

abstract class Expr {
  def eval: Int = this match {
    case Number(n) => n
    case Sum(e1, e2) => e1.eval + e2.eval
  }
}
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr

Добавление нового метода является локальным изменением.

abstract class Expr {
  def eval: Int = this match {
    case Number(n) => n
    case Sum(e1, e2) => e1.eval + e2.eval
  }
  def print = this match {
    case Number(n) => Console.print(n)
    case Sum(e1,e2) => {
      Console.print("(")
      print(e1)
      Console.print("+")
      print(e2)
      Console.print(")")
    }
  }
}

Добавление нового класса Prod потенциально может изменить все сопоставления с образцом.

abstract class Expr {
  def eval: Int = this match {
    case Number(n) => n
    case Sum(e1, e2) => e1.eval + e2.eval
    case Prod(e1,e2) => e1.eval * e2.eval
  }
  def print = this match {
    case Number(n) => Console.print(n)
    case Sum(e1,e2) => {
      Console.print("(")
      print(e1)
      Console.print("+")
      print(e2)
      Console.print(")")
    }
    case Prod(e1,e2) => ...
  }
}

Стенограмма из видеолектуры 4.6 Сопоставление с образцом

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

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

Если вы в основном создаете новые подклассы, то на самом деле решение объектно-ориентированной декомпозиции имеет преимущество. Причина в том, что очень просто и очень локально изменить просто создать новый подкласс с методом eval , где, как и в функциональном решении, вам придется вернуться назад и изменить код внутри метода eval и добавить новое дело к нему.

С другой стороны, , если то, что вы делаете, создаст много новых методов, но сама иерархия классов будет оставаться относительно стабильной, тогда сопоставление с образцом на самом деле выгодно. Потому что, опять же, каждый новый метод в решении сопоставления с образцом является просто локальным изменением , независимо от того, помещаете ли вы его в базовый класс или, возможно, даже вне иерархии классов. В то время как новый метод, такой как show в объектно-ориентированной декомпозиции, потребует нового приращения каждого подкласса. Так что будет больше деталей, К которым ты должен прикоснуться.

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

Помните: мы должны использовать это как отправную точку, а не как единственный критерий.

enter image description here

0 голосов
/ 03 июня 2018

Я цитирую это из Scala cookbook по Alvin Alexander главе 6: objects.

Это одна из многих вещей, которые мне показались интересными в этой книге.

Чтобы обеспечить несколько конструкторов для класса case, важно знать, что фактически делает объявление класса case.

case class Person (var name: String)

Если вы посмотрите на код, сгенерированный компилятором Scala для класса caseНапример, вы увидите, что он создает два выходных файла: Person $ .class и Person.class.Если вы разберете Person $ .class с помощью команды javap, вы увидите, что он содержит метод apply, наряду со многими другими:

$ javap Person$
Compiled from "Person.scala"
public final class Person$ extends scala.runtime.AbstractFunction1 implements scala.ScalaObject,scala.Serializable{
public static final Person$ MODULE$;
public static {};
public final java.lang.String toString();
public scala.Option unapply(Person);
public Person apply(java.lang.String); // the apply method (returns a Person) public java.lang.Object readResolve();
        public java.lang.Object apply(java.lang.Object);
    }

Вы также можете разобрать Person.class, чтобы посмотреть, что в нем содержится.Для такого простого класса он содержит еще 20 методов;это скрытое раздувание - одна из причин, по которой некоторым разработчикам не нравятся классы case.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...