Podobała mi się odpowiedź Rolanda Ewalda, ponieważ opisał ją za pomocą bardzo prostego przypadku użycia aliasu typu, a po więcej szczegółów przedstawił bardzo fajny tutorial. Ponieważ jednak w tym poście wprowadzono inny przypadek użycia nazwany typami składowymi , chciałbym wspomnieć o najbardziej praktycznym przypadku użycia, który bardzo mi się spodobał: (ta część jest wzięta stąd :)
Typ abstrakcyjny:
type T
T powyżej mówi, że ten typ, który będzie używany, jest jeszcze nieznany iw zależności od konkretnej podklasy zostanie zdefiniowany. Najlepszym sposobem zawsze na zrozumienie koncepcji programowania jest podanie przykładu: Załóżmy, że masz następujący scenariusz:
Tutaj otrzymasz błąd kompilacji, ponieważ metoda eat w klasach Cow i Tiger nie przesłania metody eat w klasie Animal, ponieważ ich typy parametrów są różne. To Trawa w klasie Krowa i Mięso w klasie Tygrys vs. Jedzenie w klasie Zwierzę, która jest superklasa i wszystkie podklasy muszą być zgodne.
Wracając do abstrakcji typów, korzystając z poniższego diagramu i dodając po prostu abstrakcję typu, możesz zdefiniować typ danych wejściowych zgodnie z samą podklasą.
Teraz spójrz na następujące kody:
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
Kompilator jest zadowolony i ulepszamy nasz projekt. Możemy karmić naszą krowę krową. Odpowiednie jedzenie i kompilator uniemożliwiają nam karmienie krowy pokarmem odpowiednim dla Tygrysa. Ale co, jeśli chcemy odróżnić rodzaj krowy1 OdpowiedniFood i krowa2 SuitabeFood. Innymi słowy, byłoby bardzo przydatne w niektórych scenariuszach, gdyby ścieżka, po której docieramy do typu (oczywiście przez obiekt), miała w zasadzie znaczenie. Dzięki zaawansowanym funkcjom w scali możliwe jest:
Typy zależne od ścieżki:
obiekty Scala mogą mieć typy jako elementy członkowskie. Znaczenie typu zależy od ścieżki, której używasz, aby uzyskać do niego dostęp. Ścieżka jest określana przez odniesienie do obiektu (inaczej instancji klasy). Aby zaimplementować ten scenariusz, musisz zdefiniować klasę Grass wewnątrz krowy, tj. Cow to klasa zewnętrzna, a Grass to klasa wewnętrzna. Struktura będzie wyglądać następująco:
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 = {}
}
Teraz, jeśli spróbujesz skompilować ten kod:
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
W linii 4 zobaczysz błąd, ponieważ Grass jest teraz wewnętrzną klasą Cow, dlatego aby utworzyć instancję Grass, potrzebujemy obiektu krowa, który określa ścieżkę. Zatem 2 krowy dają początek 2 różnym ścieżkom. W tym scenariuszu krowa2 chce jeść tylko jedzenie specjalnie dla niego stworzone. Więc:
cow2 eat new cow2.SuitableFood
Teraz wszyscy są szczęśliwi :-)