Есть ли ситуации, когда вы должны предпочесть класс без регистра?
Мартин Одерский дает нам хорошую отправную точку в своем курсе « Принципы функционального программирования в Scala» (лекция 4.6 - Сопоставление с образцом), который мы могли бы использовать, когда нам нужно выбирать между классом и классом case. Глава 7 Scala By Example содержит тот же пример.
Скажем, мы хотим написать интерпретатор для арифметических выражений. Для простоты изначально мы ограничиваемся числами и операциями +. Такие выражения могут быть представлены в виде иерархии классов с абстрактным базовым классом Expr в качестве корня и двумя подклассами Number и Sum. Тогда выражение 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(")")
}
}
Та же проблема решена с case-классами.
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 в объектно-ориентированной декомпозиции, потребует нового приращения каждого подкласса. Так что было бы больше частей, к которым вы должны прикоснуться.
Таким образом, проблема этой расширяемости в двух измерениях, когда вы, возможно, захотите добавить новые классы в иерархию, или вы можете захотеть добавить новые методы, или, может быть, и то и другое, была названа проблемой выражения .
Помните: мы должны использовать это как отправную точку, а не как единственный критерий.