Есть ли ситуации, когда вы предпочитаете не-кейс-класс?
Мартин Одерски дает нам хорошую отправную точку в своем курсе Принципы функционального программирования в 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 в объектно-ориентированной декомпозиции, потребует нового приращения каждого подкласса. Так что будет больше деталей, К которым ты должен прикоснуться.
Таким образом, проблема этой расширяемости в двух измерениях, где вы можете захотеть добавить новые классы в иерархию, или вы можете захотеть добавить новые методы, или, возможно, оба, была названа проблема выражения .
Помните: мы должны использовать это как отправную точку, а не как единственный критерий.