Mi è piaciuta la risposta di Roland Ewald poiché mi ha descritto con un semplicissimo caso d'uso di alias di tipo, e per maggiori dettagli ho introdotto un tutorial molto carino. Tuttavia, poiché in questo post viene introdotto un altro caso d'uso denominato membri di tipo , vorrei menzionarne il caso d'uso più pratico, che mi è piaciuto molto: (questa parte è tratta da qui :)
Tipo astratto:
type T
T sopra dice che questo tipo che verrà usato, è ancora sconosciuto, e in base alla sottoclasse concreta, verrà definito. Il modo migliore per comprendere sempre i concetti di programmazione è fornire un esempio: supponiamo di avere il seguente scenario:
Qui otterrai l'errore di compilazione, perché il metodo eat nelle classi Cow e Tiger non sovrascrive il metodo eat nella classe Animal, perché i loro tipi di parametri sono diversi. È Grass in class Cow e Meat in class Tiger vs. Food in class Animal che è super class e tutte le sottoclassi devono conformarsi.
Ora torniamo al tipo di astrazione, con il seguente diagramma e semplicemente aggiungendo un tipo di astrazione, è possibile definire il tipo di input, in base alla sottoclasse stessa.
Ora guarda i seguenti codici:
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
Il compilatore è felice e miglioriamo il nostro design. Siamo in grado di nutrire la nostra mucca con la mucca.AdattoCibo e compilatore ci impediscono di nutrire la mucca con il cibo adatto per Tiger. E se volessimo fare la differenza tra il tipo di cow1 AdattoFood e cow2 SuitabeFood. In altre parole, sarebbe molto utile in alcuni scenari se il percorso attraverso il quale raggiungiamo il tipo (ovviamente tramite l'oggetto) è fondamentalmente importante. Grazie alle funzionalità avanzate di scala, è possibile:
Tipi dipendenti dal percorso: gli
oggetti Scala possono avere tipi come membri. Il significato del tipo dipende dal percorso utilizzato per accedervi. Il percorso è determinato dal riferimento a un oggetto (ovvero un'istanza di una classe). Per implementare questo scenario, è necessario definire la classe Grass all'interno della Cow, ovvero Cow è la classe esterna e Grass è la classe interna. La struttura sarà così:
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 = {}
}
Ora se provi a compilare questo codice:
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
Nella riga 4 vedrai un errore perché Grass è ora una classe interna di Cow, quindi, per creare un'istanza di Grass, abbiamo bisogno di un oggetto cow e questo oggetto cow determina il percorso. Quindi 2 oggetti di mucca danno origine a 2 percorsi diversi. In questo scenario, cow2 vuole solo mangiare cibo appositamente creato per questo. Così:
cow2 eat new cow2.SuitableFood
Ora tutti sono felici :-)