Una de las características de Scala más importantes es la posibilidad de hacer mixin. Mediante mixin podemos tener herencia múltiple de varios traits. ¿Traits? ¿Mixin? ¿Lo qué? Bueno, vamos a empezar poco a poco 🙂
¿Qué son los traits?
Los Traits (o rasgos en la lengua de Cervantes) son un aglutinamiento de atributos y métodos propios de un rasgo o característica. Son similares a las interfaces de Java con la salvedad de que se pueden implementar parcialmente, es decir, solo algunos de sus métodos o valores.
Sirve para añadir comportamientos a otras entidades. Por ejemplo, vamos a implementar el trait CanFly en el que definiremos los valores y métodos necesarios para cualquier criatura voladora:
trait CanFly { val altitude: Float def fly() = println("I'm flying!!!") }
Como se puede observar, en este trait existe una variable altitude que está sin definir, y un método fly que sí está definido. Podemos instanciar un trait utilizando una clase anónima de la siguiente forma:
val myFlyingAnimal = new CanFly {}
Sin embargo, como la variable altitude no está definida, este trozo de código provocará un error de compilación. Para poder instanciar un trait, es necesario implementar todos sus valores y métodos:
val myFlyingAnimal = new CanFly { val altitude = 100.0f }
¿Cómo podemos utilizar los traits?
A pesar de que ya sabemos como instanciar traits mediante clases anónimas, no es uno de sus usos más comunes.
Los traits se usan para añadir comportamientos a nuestras entidades. Para ello podemos, mediante composición, hacer mixin de uno o varios traits a la hora de definir otro trait, una clase, una case class …
Para ello, se utilizan dos palabras reservadas: extends y with. El primer trait tendrá como predecesor la palabra extends, el resto se irán incluyendo con with.
case class Pigeon extends CanFly case class Superman extends Superhero with CanFly
A esto es a lo que llamamos Mixin. ¿A que ahora todo tiene más sentido?
¿Y cómo trabajan los mixin con el problema del diamante?
Primero vamos a crear un escenario con el problema. Tenemos un trait Animal que tiene un método sin implementar que devuelve el sonido del animal.
trait Animal { def sound: String }
Por otro lado, tenemos dos especificaciones del animal: un gato y un perro.
trait Cat extends Animal { override def sound = "Miau" } trait Dog extends Animal { override def sound = "Guau" }
Haciendo un esfuerzo de imaginación sin igual, vamos a imaginar que un zorro es la mezcla entre un gato y un perro.
case class Fox extends Cat with Dog
Pero, si intentamos imprimir el sonido del zorro….
val myFox = Fox() println(myFox.sound)
Pues en este caso, nuestro zorro ladrará. ¿Por qué? Por que el último trait con el que se ha hecho mixin es Dog. Primer se utiliza la implementación de Cat, pero más tarde es sobrescrita por el siguiente trait definido, es decir, Dog. Por tanto podemos decir, que los traits se van instanciando en el orden en el que se han declarado.
¡Y ya está! ¿Qué hemos aprendido hoy? Que los traits y los mixins son bastante útiles….y que los zorros, en ocasiones, ladran.
[…] de unas cuantos meses con el blog ya hemos podido saber donde podemos usar traits, para qué sirven los implícitos o qué narices son eso de los tipos genéricos. Hoy vamos a […]
Me gustaMe gusta