Me gustó la respuesta de Roland Ewald, ya que describió con un caso de uso muy simple de alias de tipo, y para más detalles introdujo un tutorial muy agradable. Sin embargo, dado que se introduce otro caso de uso en esta publicación llamado miembros de tipo , me gustaría mencionar el caso de uso más práctico, que me gustó mucho: (esta parte está tomada de aquí :)
Tipo de resumen:
type T
T arriba dice que este tipo que se va a usar, aún se desconoce, y dependiendo de la subclase concreta, se definirá. La mejor manera de entender los conceptos de programación es proporcionar un ejemplo: suponga que tiene el siguiente escenario:
Aquí obtendrá un error de compilación, porque el método eat en las clases Cow y Tiger no anula el método eat en la clase Animal, porque sus tipos de parámetros son diferentes. Es hierba en la clase vaca y carne en la clase tigre vs. alimento en la clase animal, que es súper clase y todas las subclases deben cumplir.
Ahora, de vuelta a la abstracción de tipo, mediante el siguiente diagrama y simplemente agregando una abstracción de tipo, puede definir el tipo de entrada, de acuerdo con la propia subclase.
Ahora mira los siguientes códigos:
val cow1: Cow = new Cow
val cow2: Cow = new Cow
cow1 eat new cow1.SuitableFood
cow2 eat new cow1.SuitableFood
val tiger: Tiger = new Tiger
cow1 eat new tiger.SuitableFood // Compiler error
El compilador está contento y mejoramos nuestro diseño. Podemos alimentar a nuestra vaca con vaca. La comida y el compilador adecuados nos impiden alimentar a la vaca con la comida adecuada para el tigre. Pero, ¿qué pasa si queremos hacer una diferencia entre el tipo de vaca1 AdecuadoFood y vaca2 SuitabeFood? En otras palabras, sería muy útil en algunos escenarios si la ruta por la que llegamos al tipo (por supuesto, a través del objeto) es fundamental. Gracias a las funciones avanzadas de scala, es posible:
Tipos dependientes de la ruta: los
objetos Scala pueden tener tipos como miembros. El significado del tipo depende de la ruta que use para acceder a él. La ruta está determinada por la referencia a un objeto (también conocido como una instancia de una clase). Para implementar este escenario, debe definir la clase Grass dentro de Cow, es decir, Cow es la clase externa y Grass es la clase interna. La estructura será así:
class Cow extends Animal {
class Grass extends Food
type SuitableFood = Grass
override def eat(food: this.SuitableFood): Unit = {}
}
class Tiger extends Animal {
class Meat extends Food
type SuitableFood = Meat
override def eat(food: this.SuitableFood): Unit = {}
}
Ahora, si intentas compilar este código:
1. val cow1: Cow = new Cow
2. val cow2: Cow = new Cow
3. cow1 eat new cow1.SuitableFood
4. cow2 eat new cow1.SuitableFood // compilation error
En la línea 4 verá un error porque Grass ahora es una clase interna de Cow, por lo tanto, para crear una instancia de Grass, necesitamos un objeto cow y este objeto cow determina el camino. Entonces 2 objetos de vaca dan lugar a 2 caminos diferentes. En este escenario, cow2 solo quiere comer alimentos especialmente creados para ello. Entonces:
cow2 eat new cow2.SuitableFood
Ahora todos están felices :-)