非ケース クラスを使用する必要がある状況はありますか?
Martin Odersky は、クラスとケース クラスのどちらかを選択する必要がある場合に使用できる、Scala での関数型プログラミングの原則(講義 4.6 - パターン マッチング) のコースで良い出発点を提供してくれます。Scala By Exampleの第 7 章には、同じ例が含まれています。
たとえば、算術式のインタプリタを書きたいとします。最初は単純にするために、数字と + 演算だけに制限します。このような式は、抽象基底クラス Expr をルートとし、2 つのサブクラス 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 パターンマッチング
これらのデザインはどちらも完璧に優れており、どちらを選択するかはスタイルの問題になる場合がありますが、それでも重要な基準がいくつかあります.
基準の 1 つは、式の新しいサブクラスを作成する頻度が高いか、新しいメソッドを作成する頻度が高いかということです。したがって、システムの将来の拡張性と可能な拡張パスを調べる基準です。
あなたがしていることのほとんどが新しいサブクラスの作成である場合、実際にはオブジェクト指向の分解ソリューションが有利です。その理由は、eval メソッドを使用して新しいサブクラスを作成するのは非常に簡単で、非常にローカルな変更であるためです。機能的なソリューションの場合と同様に、前に戻って eval メソッド内のコードを変更し、新しいケースを追加する必要があります。それに。
一方、新しいメソッドを大量に作成するが、クラス階層自体が比較的安定している場合は、パターン マッチングが実際に有利です。繰り返しますが、パターン マッチング ソリューションの各新しいメソッドは、それを基本クラスに配置するか、クラス階層の外に配置するかにかかわらず、ローカルな変更にすぎないためです。オブジェクト指向分解の show などの新しいメソッドでは、各サブクラスで新しいインクリメントが必要になります。触れないといけない部分が増えるから。
そのため、新しいクラスを階層に追加したり、新しいメソッドを追加したり、あるいはその両方を行う場合の 2 次元での拡張性の問題は、式の問題と呼ばれています。
覚えておいてください: これは出発点のように使用する必要があり、唯一の基準ではありません。
