ケース以外のクラスを優先する必要がある状況はありますか?
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次元でのこの拡張性の問題は、式の問題と呼ばれています。
覚えておいてください。これは、唯一の基準ではなく、出発点のように使用する必要があります。