Если вы пишете код, в котором используется множество красивых, неизменяемых структур данных, классы case кажутся находкой, предоставляя вам все следующее бесплатно с помощью всего одного ключевого слова:
- По умолчанию все неизменяемо
- Геттеры определяются автоматически
- Достойная реализация toString ()
- Соответствует equals () и hashCode ()
- Сопутствующий объект с методом unapply () для сопоставления
Но каковы недостатки определения неизменяемой структуры данных как класса case?
Какие ограничения он накладывает на класс или его клиентов?
Есть ли ситуации, когда вы должны предпочесть класс без регистра?
scala
case-class
Грэм Ли
источник
источник
Ответы:
Один большой недостаток: классы case не могут расширять класс case. Это ограничение.
Другие преимущества, которые вы упустили, перечислены для полноты: совместимость с сериализацией / десериализацией, отсутствие необходимости использовать ключевое слово «новое» для создания.
Я предпочитаю классы без регистра для объектов с изменяемым состоянием, частным состоянием или без состояния (например, большинство одноэлементных компонентов). Кейс-классы почти для всего остального.
источник
Сначала хорошие моменты:
По умолчанию все неизменяемо
Да, и даже может быть переопределен (с помощью
var
), если вам это нужноГеттеры определяются автоматически
Возможно в любом классе с префиксом params с
val
Достойная
toString()
реализацияДа, очень полезно, но при необходимости можно выполнить вручную на любом классе
Соответствует
equals()
иhashCode()
В сочетании с простым сопоставлением с образцом это основная причина, по которой люди используют классы case
Сопутствующий объект с
unapply()
методом сопоставленияТакже можно сделать вручную на любом классе, используя экстракторы
Этот список также должен включать сверхмощный метод копирования, одно из лучших решений Scala 2.8.
Тогда плохо, есть только несколько реальных ограничений с классами case:
Вы не можете определить
apply
в сопутствующем объекте ту же сигнатуру, что и метод, созданный компилятором.Однако на практике это редко бывает проблемой. Изменение поведения сгенерированного метода apply гарантированно удивит пользователей, и его следует настоятельно не рекомендовать, единственное оправдание для этого - проверка входных параметров - задачу лучше всего выполнять в теле основного конструктора (что также делает проверку доступной при использовании
copy
)Вы не можете создать подкласс
Верно, хотя класс case все еще может быть потомком. Один из распространенных шаблонов - построение иерархии классов признаков с использованием классов случаев в качестве конечных узлов дерева.
Также стоит отметить
sealed
модификатор. Любой подкласс признака с этим модификатором должен быть объявлен в том же файле. При сопоставлении с образцом экземпляров признака компилятор может предупредить вас, если вы не проверили все возможные конкретные подклассы. В сочетании с классами case это может предложить вам очень высокий уровень уверенности в вашем коде, если он компилируется без предупреждения.Как подкласс Product, классы case не могут иметь более 22 параметров.
Нет реального обходного пути, кроме как прекратить злоупотреблять классами с таким количеством параметров :)
Также...
Еще одно ограничение, которое иногда отмечается, заключается в том, что Scala (в настоящее время) не поддерживает ленивые параметры (например,
lazy val
s, но как параметры). Обходной путь - использовать параметр по имени и назначить его ленивому значению в конструкторе. К сожалению, параметры по имени не смешиваются с сопоставлением с образцом, что не позволяет использовать этот метод с классами case, поскольку он нарушает работу экстрактора, созданного компилятором.Это актуально, если вы хотите реализовать высокофункциональные ленивые структуры данных, и, надеюсь, будет решено с добавлением ленивых параметров в будущую версию Scala.
источник
Я думаю, что здесь применим принцип TDD: не переусердствуйте. Когда вы объявляете что-то как a
case class
, вы декларируете большую функциональность. Это снизит гибкость при смене класса в будущем.Например, a
case class
имеетequals
метод над параметрами конструктора. Вы можете не заботиться об этом, когда впервые пишете свой класс, но в последнем случае вы можете решить, что вы хотите, чтобы равенство игнорировало некоторые из этих параметров, или сделало что-то немного другое. Однако клиентский код может быть написан в среднем за время, которое зависит отcase class
равенства.источник
Мартин Одерский дает нам хорошую отправную точку в своем курсе « Принципы функционального программирования в Scala» (лекция 4.6 - Сопоставление с образцом), который мы могли бы использовать, когда нам нужно выбирать между классом и классом case. Глава 7 Scala By Example содержит тот же пример.
Кроме того, добавление нового класса Prod не влечет за собой изменений существующего кода:
Напротив, добавление нового метода требует модификации всех существующих классов.
Та же проблема решена с case-классами.
Добавление нового метода - это локальное изменение.
Добавление нового класса Prod потенциально требует изменения всего сопоставления с образцом.
Стенограмма видеолекции 4.6 Сопоставление с образцом
Помните: мы должны использовать это как отправную точку, а не как единственный критерий.
источник
Я цитирую это из
Scala cookbook
поAlvin Alexander
главе 6:objects
.Это одна из многих вещей, которые мне показались интересными в этой книге.
Чтобы предоставить несколько конструкторов для класса case, важно знать, что на самом деле делает объявление класса case.
Если вы посмотрите на код, который компилятор Scala генерирует для примера класса case, вы увидите, что он создает два выходных файла: Person $ .class и Person.class. Если вы дизассемблируете Person $ .class с помощью команды javap, вы увидите, что он содержит метод apply, а также многие другие:
Вы также можете разобрать Person.class, чтобы увидеть, что он содержит. Для такого простого класса он содержит дополнительно 20 методов; это скрытое раздувание - одна из причин, по которой некоторым разработчикам не нравятся классы case.
источник