Scala desde la perspectiva de C# y JavaScript, tercera parte

En los artículos anteriores de esta serie, veíamos una pequeña introducción a Scala, y hacíamos una kata para comprobar que habíamos entendido la sintaxis. En este artículo veremos dos construcciones del lenguaje que resultan bastante interesantes, llamadas traits y case classes.

Traits

Podemos entender los traits como una mezcla entre interfaces y clases abstractas, ya que podemos definir un contrato como haríamos con una interfaz, y por otra parte podemos definir también una implementación.

A diferencia de Java, Scala nos permite implementar varias traits, y podemos utilizarlas para agregar características adicionales a nuestro código. Veamos un ejemplo.

En este caso vamos a definir una clase base llamada Employee, que tiene un salario, y luego vamos a definir varios traits, algunos solamente con valores, y otros con pequeñas comprobaciones.

  • El trait Temporary calcula el mes final del contrato
  • El trait Authority nos permite saber si un empleado tiene gente a su cargo
  • El trait Remote nos permite establecer una localización y una zona horaria
  • Finalmente, el trait InOffice nos permite establecer un despacho.
abstract class Employee {
def salary: Int
}
trait InOffice {
def desk: String
}
trait Remote {
def location: String
def timeZone: String
}
trait Temporary {
def startDate: Int
def stay: Int
def endDate(): Int = (startDate + stay) % 12
}
trait Authority {
def directReports: Array[Employee]
def hasDirectReports() :Boolean = directReports.length != 0
}

view raw
traits.scala
hosted with ❤ by GitHub

Con estos cuatro traits podemos componer las clases que forman a nuestros empleados, donde tenemos a Developer, a Manager y a Intern:

class Intern (salary_param: Int, desk_param: String, startDate_param: Int) extends Employee with InOffice with Temporary {
val salary = salary_param
val desk = desk_param
val stay = 6
val startDate = startDate_param
}
class Developer (salary_param: Int, desk_param: String) extends Employee with InOffice {
val salary = salary_param
val desk = desk_param
}
class Manager (salary_param: Int, location_param: String, reports: Array[Employee]) extends Employee with Remote with Authority {
val salary = salary_param
val location = location_param
val directReports = reports
}

view raw
traits_impl.scala
hosted with ❤ by GitHub

Como vemos en el ejemplo, podemos utilizar los traits para definir características adicionales a nuestras clases, y podemos también agregar lógica dentro de las mismas, mezclando los conceptos de interfaz y clase abstracta.

Case classes

La otra cara de la moneda son las Case Classes, que podemos encontrar cuando definimos jerarquías, pero sobre todo las podemos encontrar cuando tenemos un número específico de entidades y lo que realmente cambia es lo que hacemos con ellas. Recuperando el mismo caso de empleados, becarios y managers, vamos a redefinirlo como Case Classes:

abstract class Employee
case class Manager(startYear: Int, direct: Array[Employee]) extends Employee
case class Dev(startYear: Int) extends Employee
case class Intern(stay: Int) extends Employee

view raw
case_classes.scala
hosted with ❤ by GitHub

En este caso hemos simplificado la manera de definir los diferentes datos, y solamente utilizaremos la fecha inicial para Dev y Manager, y el tiempo total de estancia para Intern. En este caso, podemos asumir que nuestro conjunto de datos está fijo.

Sin embargo, ¿qué pasaría si necesitáramos agregar información de nóminas a nuestra aplicación? Si recurriéramos a la herencia, deberíamos implementar un método “salary” o similar, para cada una de las clases, sin embargo, como estamos usando “case classes” podemos crear un único método que las compruebe todas, y se comporte de manera consistente:

object Payroll {
def printSalary(employee: Employee) {
val currentYeay = 2001
employee match {
case Intern(stay) =>
println(stay * 300)
case Dev(startYear) =>
println(1000 + (currentYeay startYear) * 500)
case Manager(startYear, direct) =>
println(1000 + (currentYeay startYear) * 500 + direct.length * 20)
}
}
}

view raw
payroll.scala
hosted with ❤ by GitHub

En este caso, el método nos devolverá distintos resultados en función de la clase que estemos aplicando y los datos que contenga la misma, permitiendo una separación entre las clases que contienen los datos de los métodos para manipularlos.

Este enfoque tiene sus inconvenientes, y es que si tenemos que agregar un nuevo tipo de dato (por ejemplo, External) tendremos que cambiar todos los métodos auxiliares para soportar el nuevo caso, así que se reduce a elegir en función de las necesidades de nuestro proyecto.

Conclusiones

Construcciones como Case Classes o Traits nos permiten establecer restricciones de una manera sencilla, las traits nos permiten mezclar conceptos de interfaces y clases, y las case classes nos permiten separar los datos de nuestra clase de las transformaciones que hacemos con los mismos.

En C# tenemos a nuestra disposición los métodos de extensión, con los que podemos lograr un comportamiento similar que con las case classes, y que hemos visto con anterioridad en este artículo “Clases abstractas VS interfaces + métodos de extensión“.

En el próximo artículo de la serie veremos más construcciones y, como siempre, más ejemplos.

Para más información:

Autor: Roberto Luis Bisbé

Software Developer, Computer Engineer

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios .