¿Hay situaciones en las que debería preferir una clase que no sea de casos?
Martin Odersky nos da un buen punto de partida en su curso Principios de programación funcional en Scala (Lección 4.6 - Coincidencia de patrones) que podríamos usar cuando debemos elegir entre clase y clase de caso. El capítulo 7 de Scala By Example contiene el mismo ejemplo.
Digamos, queremos escribir un intérprete para expresiones aritméticas. Para mantener las cosas simples inicialmente, nos limitamos a números y operaciones +. Estas expresiones pueden representarse como una jerarquía de clases, con una clase base abstracta Expr como raíz y dos subclases Number y Sum. Entonces, una expresión 1 + (3 + 7) se representaría como
nueva suma (nuevo número (1), nueva suma (nuevo número (3), nuevo número (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
}
Además, agregar una nueva clase Prod no implica ningún cambio en el código existente:
class Prod(e1: Expr, e2: Expr) extends Expr {
def eval: Int = e1.eval * e2.eval
}
Por el contrario, agregar un nuevo método requiere la modificación de todas las clases existentes.
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(")")
}
}
El mismo problema resuelto con clases de casos.
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
Agregar un nuevo método es un cambio local.
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(")")
}
}
}
Agregar una nueva clase Prod requiere potencialmente cambiar todas las coincidencias de patrones.
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) => ...
}
}
Transcripción de la videolección 4.6 Coincidencia de patrones
Ambos diseños están perfectamente bien y elegir entre ellos a veces es una cuestión de estilo, pero sin embargo hay algunos criterios que son importantes.
Un criterio podría ser, ¿ está creando más a menudo nuevas subclases de expresión o está creando más a menudo nuevos métodos? Por lo tanto, es un criterio que mira la extensibilidad futura y el posible pase de extensión de su sistema.
Si lo que haces es principalmente crear nuevas subclases, entonces la solución de descomposición orientada a objetos tiene la ventaja. La razón es que es muy fácil y un cambio muy local simplemente crear una nueva subclase con un método eval , donde como en la solución funcional, tendrías que regresar y cambiar el código dentro del método eval y agregar un nuevo caso. lo.
Por otro lado, si lo que va a hacer será crear muchos métodos nuevos, pero la jerarquía de clases en sí se mantendrá relativamente estable, entonces la coincidencia de patrones es realmente ventajosa. Porque, nuevamente, cada nuevo método en la solución de coincidencia de patrones es solo un cambio local , ya sea que lo coloque en la clase base o tal vez incluso fuera de la jerarquía de clases. Mientras que un nuevo método como show en la descomposición orientada a objetos requeriría un nuevo incremento en cada subclase. Entonces habría más partes, que tienes que tocar.
Entonces, la problemática de esta extensibilidad en dos dimensiones, donde es posible que desee agregar nuevas clases a una jerarquía, o puede que desee agregar nuevos métodos, o tal vez ambos, se ha denominado problema de expresión .
Recuerde: debemos usar esto como un punto de partida y no como el único criterio.