케이스가 아닌 클래스를 선호해야하는 상황이 있습니까?
Martin Odersky는 클래스와 케이스 클래스 중에서 선택해야 할 때 사용할 수 있는 Scala의 Functional Programming Principles (강의 4.6-Pattern Matching) 과정에서 좋은 출발점을 제공 합니다. Scala By Example의 7 장 에는 동일한 예제 가 포함되어 있습니다.
산술 표현을위한 인터프리터를 작성하고 싶습니다. 처음에는 단순하게 유지하기 위해 숫자와 + 연산으로 만 제한합니다. 이러한 표현식은 루트로 추상 기본 클래스 Expr과 두 개의 하위 클래스 인 Number 및 Sum을 사용하여 클래스 계층 구조로 표현 될 수 있습니다. 그런 다음 식 1 + (3 + 7)은 다음과 같이 표현됩니다.
new Sum (new Number (1), new Sum (new Number (3), new Number (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와 같은 새로운 방법은 새로운 증가가 필요하지만 각 하위 클래스입니다. 그래서 더 많은 부분을 만져야합니다.
따라서 계층 구조에 새 클래스를 추가하거나 새 메서드를 추가하거나 둘 다 추가 할 수있는 2 차원 확장 성의 문제를 expression problem 이라고 명명 했습니다 .
기억하십시오 : 우리는 이것을 유일한 기준이 아닌 시작점으로 사용해야합니다.