W Verse można utworzyć klasę, która stanowi rozszerzenie definicji innej klasy, dodając lub modyfikując pola i metody tej drugiej klasy. Jest to często nazywane podklasowaniem lub dziedziczeniem, ponieważ jedna klasa dziedziczy definicje z drugiej klasy.
Spójrzmy na urządzenie kreatora klas jako przykład podklasowania. Za pomocą urządzenia kreatora klas możesz tworzyć klasy postaci dla postaci graczy, które pozwalają definiować atrybuty i ekwipunek specyficzny dla danej klasy postaci, np. tank lub DPS (obrażenia na sekundę).
![]() |
![]() |
| Klasa postaci DPS utworzona za pomocą kreatora klas | Klasa postaci tank utworzona za pomocą kreatora klas |
W Verse możesz utworzyć klasę tank i klasę dps w następujący sposób:
tank := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
DamageReduction : int
dps := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
MovementMultiplier : float
Ze względu na to, że niektóre pola w obu klasach są takie same, możesz ograniczyć duplikację za pomocą nadklasy, która przechowuje wspólne właściwości i zachowania klas. Nazwijmy tę nadklasę player_character, a tank i dps niech będą podklasami player_character:
player_character := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
dps := class(player_character):
MovementMultiplier : float
tank := class(player_character):
DamageReduction : int
Ze względu na to, że klasy tank i dps są podklasami klasy player_character, automatycznie dziedziczą one pola i metody klasy player_character, więc wystarczy, że określisz, czym różni się ta klasa od nadklasy.
Na przykład w przypadku klasy dps dodawane jest tylko pole Movement Multiplier, a w przypadku klasy tank dodawane jest tylko pole DamageReduction. Ta konfiguracja przydaje się, jeśli później zmienisz współdzielone zachowania dwóch klas, ponieważ wystarczy zmienić je tylko w nadklasie.
Za pomocą Verse możesz dodać więcej zmian, aby rozróżnić klasy tank i dps, dodając metody do podklas.
Przydatnym efektem podklasowania jest to, że możesz użyć relacji między nadklasą i jej podklasami. Z racji dziedziczenia instancja tank jest wyspecjalizowaną klasą player_character, a instancja dps jest wyspecjalizowaną klasą player_character, co jest określane jako relacja is-a. Ponieważ tank i dps są podklasami tej samej nadklasy i różnią od ich wspólnej nadklasy, tank nie ma związku z dps.
Specyfikator zastąpienia
Aby utworzyć instancje klas z początkowymi wartościami, powszechną praktyką jest zastosowanie funkcji, która generuje instancje. Na przykład:
CreateDPSPlayerCharacter() : dps =
return dps{StartingShields := 0, MaxShields := 0, AllowOvershield := false, MovementMultiplier := 1.9}
CreateTankPlayerCharacter() : tank =
return tank{StartingShields := 100, MaxShields := 200, AllowOvershield := true, DamageReduction := 50}
Funkcje CreateTankPlayerCharacter() i CreateDPSPlayerCharacter() tworzą instancje z odpowiednimi wartościami początkowymi. Ewentualnie można zastąpić pola z nadklasy i przypisać wartości początkowe, dzięki czemu nie trzeba podawać tak wielu wartości początkowych podczas tworzenia instancji.
Na przykład klasa tank z poprzedniej sekcji z zastąpieniami pól może wyglądać następująco:
tank := class(player_character):
StartingShields<override> : int = 100
MaxShields<override> : int = 200
AllowOvershield<override> : logic = true
DamageReduction : int = 50
CreateTankPlayerCharacter() : tank =
return tank{}
Metody w podklasie również możesz zastąpić, co oznacza, że możesz używać metody zastępującej wszędzie tam, gdzie można użyć metody zastępowanej. Oznacza to, że:
- Metoda musi przyjmować przynajmniej jeden argument przyjmowany przez metodę zastępowaną, więc typ parametru musi być nadtypem typu parametru zastępowanej funkcji.
- Metoda nie może zwracać wartości, której nie mogłaby mieć metoda zastępowana, więc zwracany typ musi być podtypem zwracanego typu metody zastępowanej.
- Metoda nie może mieć więcej efektów niż metoda zastępowana, więc specyfikator efektów musi być podtypem specyfikatora efektów metody zastępowanej.
Identyfikator Super
Podobnie jak w przypadku identyfikatora Self, za pomocą identyfikatora (super:) można uzyskać dostęp do implementacji pól i metod nadklasy. Aby można było użyć identyfikatora (super:), pole lub metoda muszą być zaimplementowane w definicji nadklasy.
pet := class():
Sound : string
Speak() : void =
Log(Sound)
cat := class(pet):
Sound<override> : string = "Miau"
Speak<override>() : void =
(super:)Speak() # W rejestrze wyjściowym pojawia się "Miau"
Log("Mrrau") # W rejestrze wyjściowym pojawia się "Mrrau"
Wyrażenia block w ciele podklasy
Wszelkie wyrażenia block znajdujące się w ciele podklasy zostaną wykonane po wyrażeniach block określonych w ciele nadklasy. Na przykład w poniższym kodzie, gdy tworzona jest instancja klasy cat o nazwie MrSnuffles, najpierw wykonywana jest funkcja Speak(), a następnie Purr().
pet := class():
Speak() : void =
...
block:
Speak()
cat := class(pet):
Purr() : void =
...
block:
Purr()
MrSnuffles := cat{}
Specyfikator abstract
Gdy klasa lub metoda klasy zawiera specyfikator abstract, nie można utworzyć instancji danej klasy. Klasy abstrakcyjne służą jako nadklasy z częściową implementacją lub jako wspólny interfejs. Jest to przydatne, gdy tworzenie instancji nadklasy nie ma sensu i nie chcemy dublować właściwości oraz zachowań w podobnych klasach.
W poniższym przykładzie, ponieważ pet jest pojęciem abstrakcyjnym, instancja klasy pet nie jest wystarczająco konkretna, ale pet cat lub pet dog ma sens, więc te podklasy nie są oznaczone jako abstrakcyjne.
pet := class<abstract>():
Speak() : void
cat := class(pet):
Speak() : void =
...
dog := class(pet):
Speak() : void =
...

