W Verse klasa to szablon do tworzenia obiektów o podobnych zachowaniach i właściwościach. Jest to typ złożony, co oznacza, że zawiera dane innych typów i funkcji, które mogą operować na tych danych.
Klasy są hierarchiczne, co oznacza, że klasa może dziedziczyć informacje z klasy bazowej (nadklasy) i przekazywać je swoim klasom pochodnym (podklasom). Klasy mogą być niestandardowym typem zdefiniowanym przez użytkownika. Porównaj z instancją.
Załóżmy na przykład, że w swojej grze chcesz mieć wiele kotów. Kot ma imię, jest w określonym wieku i potrafi miauczeć. Klasa kota może wyglądać tak:
cat := class:
Name : string
var Age : int = 0
Sound : string
Meow() : void = DisplayMessage(Sound)Definicje zmiennych, które są zagnieżdżone wewnątrz klasy, określają pola tej klasy. Funkcje zdefiniowane wewnątrz klasy mogą być również nazywane metodami. Pola i metody są nazywane elementami członkowskimi klasy. W powyższym przykładzie Sound jest polem, a Meow jest metodą klasy cat.
Pola klasy mogą, ale nie muszą mieć domyślnej wartości, lub mogą mieć tylko zdefiniowany typ, który ogranicza wartości, jakie może mieć pole. W powyższym przykładzie Name nie ma wartości domyślnej, natomiast Age ma. Wartość może być określona przy użyciu wyrażenia, które ma efekty <converges> . Wyrażenie wartości domyślnej może nie używać identyfikatora Self, o czym dowiesz się poniżej.
Na przykład, powiedzmy, że chcesz, aby twoje koty mogły przechylać głowę. Obrót początkową można zainicjować HeadTilt w poniższym kodzie przy użyciu metody IdentityRotation(), ponieważ zawiera ona specyfikator <converges> i gwarantuje zakończenie bez efektów ubocznych.
cat := class:
...
# A valid expression
HeadTilt:rotation = IdentityRotation()Konstruowanie klasy
Za pomocą klasy, która określa, czym jest kot i co potrafi, możesz skonstruować instancję klasy z archetypu. Archetyp określa wartości pól klasy. Stwórzmy na przykład starego kota o imieniu Percy z klasy cat:
OldCat := cat{Name := ”Percy”, Age := 20, Sound:= ”Rrrr”}W tym przykładzie archetypem jest część pomiędzy { and }. Nie musi określać wartości dla wszystkich pól klasy, ale musi przynajmniej określić wartości dla wszystkich pól, które nie mają wartości domyślnej. Jeśli jakiekolwiek pole zostanie pominięte, to skonstruowana instancja będzie miała wartość domyślną dla tego pola.
W tym przypadku pole Age klasy cat ma przypisaną wartość domyślną (0). Ponieważ pole ma wartość domyślną, nie jest wymagane podawanie jego wartości podczas konstruowania instancji klasy. Pole jest zmienną, co oznacza, że chociaż możesz podać wartość podczas konstruowania, to wartość tej zmiennej można zmienić po zakończeniu konstruowania.
Z kolei pole Name zmiennej cat nie jest zmienną modyfikowalną, a więc jest domyślnie niemodyfikowalne. Oznacza to, że podczas konstruowania możesz nadać mu wartość domyślną, ale po skonstruowaniu nie można go zmienić: jest niemodyfikowalne.
Ponieważ klasa w Verse jest szablonem, możesz stworzyć dowolną liczbę instancji klasy cat. Stwórzmy kociaka imieniem Flash:
Kitten := cat{Name := ”Flash”, Age := 1, Sound := ”Mew”}Uzyskiwanie dostępu do pól
Skoro masz już kilka instancji klasy cat, możesz uzyskać dostęp do pola nazwy każdego kota za pomocą OldCat.Name lub Kitten.Name, a także wywołać metodę Miau każdego kota za pomocą OldCat.Meow() lub Kitten.Meow().
Oba koty mają tak samo nazwane pola, ale te pola mają różne wartości. Na przykład OldCat.Meow() i Kitten.Meow() zachowują się inaczej, ponieważ ich pola Dźwięk mają różne wartości.
U siebie
Self to specjalny identyfikator w Verse, którego można użyć w metodzie klasy, by odwołać się do instancji klasy, w której metoda została wywołana. Do innych pól instancji, w której metoda została wywołana, można tworzyć odwołania bez użycia identyfikatora Self, ale jeśli chcesz odwołać się do instancji jako całości, musisz użyć identyfikatora Self.
Gdyby na przykład DisplayMessage wymagał argumentu określającego, z którym zwierzakiem powiązać komunikat:
DisplayMessage(Pet:pet, Message:string) : void = …
cat := class:
…
Meow() : void = DisplayMessage(Self, Sound)Jeśli chcesz zainicjować głośniejszą wersję miauczenia kota, możesz oprzeć się na zmiennej Sound, która została już skonfigurowana. Nie zadziała to jednak w poniższym kodzie, ponieważ LoudSound nie może odwoływać się do elementu instancji Sound, gdyż wyrażenia wartości domyślnej nie mogą używać identyfikatora Self.
cat := class:
...
Sound : string
Meow() : void = DisplayMessage(Self, Sound)
# The following will fail since default class values
# can't reference Self
LoudSound : string = "Loud " + Self.Sound
LoudMeow() : void = DisplayMessage(Self, LoudSound)Podklasy i dziedziczenie
Klasy mogą dziedziczyć z nadklasy, która obejmuje wszystkie pola nadklasy w klasie dziedziczenia. Takie klasy są określane jako podklasa nadklasy. Na przykład:
pet := class:
Name : string
var Age : int = 0
cat := class(pet):
Sound : string
Meow() : void = DisplayMessage(Self, Sound)
dog := class(pet):
Trick : string
Użycie klasa(pet) podczas definiowania cat i dog oznacza, że dziedziczą one z klasy pet. Innymi słowy, są one podklasami klasy pet.
Ma to kilka zalet:
Zarówno psy, jak i koty mają imiona i wiek, dlatego te pola trzeba zdefiniować tylko raz, w klasie
pet. Zarówno polecat, jak i poledogodziedziczą te pola.Klasy
petmożna używać jako typu do odwoływania się do instancji dowolnej podklasypet. Jeśli na przykład chcesz napisać funkcję, która wymaga podania nazwy zwierzaka, możesz napisać ją raz dla kotów i psów oraz dla innych podklaspet, które możesz wprowadzić w przyszłości:VerseIncreaseAge(Pet : pet) : void= set Pet.Age += 1
Więcej informacji znajdziecie na stronie Podklasa.
Zastępuje
Definiując podklasę, możesz zastąpić pola zdefiniowane w nadklasie, aby bardziej sprecyzować ich typ lub zmienić ich domyślną wartość. W tym celu musisz ponownie napisać definicję pola w podklasie, ale ze specyfikatorem <override> przy jej nazwie. Na przykład do pola Lives możesz dodać pole pet z wartością domyślną 1 i nadpisać wartość domyślną dla kotów wartością 9:
pet := class:
…
Lives : int = 1
cat := class(pet):
…
Lives<override> : int = 9Wywołania metod
Gdy uzyskujesz dostęp do pola instancji klasy, uzyskujesz dostęp do wartości pola tej instancji. W przypadku metod pole jest funkcją, a zastąpienie jej zastępuje wartość pola nową funkcją. Wywołanie metody powoduje wywołanie wartości pola. Oznacza to, że wywoływana metoda jest określana przez instancję. Rozważmy następujący przykład:
pet := class:
…
OnHearName() : void = {}
cat := class(pet):
…
OnHearName<override>() : void = Meow()
dog := class(pet):
…
Jeśli napiszesz CallFor(Percy), wywoła to metodę OnHearName, jak jest to zdefiniowane przez cat. Jeśli napiszesz CallFor(Fido), gdzie Fido jest instancją klasy dog, wywoła to metodę OnHearName zdefiniowaną przez klasę dog.
Specyfikatory widoczności
Do pól i metod klas można dodać specyfikatory widoczności, aby kontrolować, kto ma do nich dostęp. Na przykład można dodać specyfikator private do pola Sound, aby tylko klasa będąca właścicielem miała dostęp do tego prywatnego pola.
cat := class:
…
Sound<private> : string
MrSnuffles := cat{Sound := "Purr"}
MrSnuffles.Sound # Error: cannot access a private fieldOto wszystkie specyfikatory widoczności, których można używać z klasami:
public: Nieograniczony dostęp.
internal: Dostęp ograniczony do bieżącego modułu. Jest to domyślny poziom widoczności.
protected (chronione): Dostęp ograniczony do bieżącej klasy i wszelkich podklas.
private: Dostęp ograniczony do bieżącej klasy.
Specyfikatory dostępu
Możesz dodać do klasy specyfikatory dostępu, aby kontrolować, kto może je tworzyć. Jest to przydatne na przykład wtedy, gdy chcesz mieć pewność, że jakaś instancja klasy może być utworzona tylko w określonym zakresie.
pets := module:
cat<public> := class<internal>:
Sound<public> : string = "Meow"
GetCatSound(InCat:pets.cat):string =
return InCat.Sound # Valid: References the cat class but does not call its constructor
MakeCat():void =
MyNewCat := pets.cat{} # Error: Invalid access of internal class constructorWywołanie konstruktora dla klasy cat poza jej modułem pets zakończy się niepowodzeniem, ponieważ słowo kluczowe class jest oznaczone jako wewnętrzne. Jest to prawdziwe, nawet jeśli sam identyfikator klasy jest oznaczony jako publiczny, co oznacza, że kod spoza modułu pets może się odwoływać do cat.
Oto wszystkie specyfikatory dostępu, których można używać ze słowem kluczowym class:
public: Nieograniczony dostęp. Jest to domyślny dostęp.
internal: Dostęp ograniczony do bieżącego modułu.
Specyfikator betonu
Gdy klasa ma specyfikator concrete, można ją skonstruować za pomocą pustego archetypu, takiego jak cat{}.. Oznacza to, że każde pole klasy musi mieć wartość domyślną. Ponadto każda podklasa klasy concrete sama musi mieć specyfikator concrete.
Na przykład:
class1 := class<concrete>:
Property : int = 0
# Error: Property isn't initialized
class2 := class<concrete>:
Property : int
# Error: class3 must also have the <concrete> specifier since it inherits from class1
class3 := class(class1):
Property : int = 0Klasa concrete może dziedziczyć bezpośrednio z klasy abstrakcyjnej, jeśli obie klasy są zdefiniowane w tym samym module. Nie dotyczy to jednak sytuacji przechodniej – klasa concrete może dziedziczyć bezpośrednio z drugiej klasy concrete w innym module, gdzie ta druga klasa concrete dziedziczy bezpośrednio z klasy abstract w swoim module.
Specyfikator unikatowy
Specyfikator unique można zastosować do klasy, aby uczynić ją klasą unikatową. Aby skonstruować instancję klasy unikatowej, Verse przydziela unikatową tożsamość do powstałej instancji. Dzięki temu można porównywać instancje unikatowych klas pod względem równości, porównując ich tożsamości. Klasy bez specyfikatora unique nie mają takiej tożsamości, dlatego można je porównywać pod względem równości tylko na podstawie wartości ich pól.
Oznacza to, że klasy unikatowe można porównywać za pomocą operatorów = oraz <> i że są one podtypami typu comparable.
Na przykład:
unique_class := class<unique>:
Field : int
Main()<decides> : void =
X := unique_class{Field := 1}
X = X # X is equal to itself
Y := unique_class{Field := 1}
X <> Y # X and Y are unique and therefore not equalSpecyfikator końcowy
Specyfikatora final można używać tylko w klasach i polach klas.
Jeśli klasa ma specyfikator final, nie można tworzyć jej podklas. W poniższym przykładzie nie można użyć klasy pet jako nadklasy, ponieważ klasa ta ma specyfikator final.
pet := class<final>():
…
cat := class(pet): # Error: cannot subclass a “final” class
…Jeśli pole ma specyfikator final, nie można zastąpić tego pola w podklasie. W poniższym przykładzie klasa cat nie może zastąpić pola Właściciel, ponieważ pole to ma specyfikator final.
pet := class():
Owner<final> : string = “Andy”
cat := class(pet):
Owner<override> : string = “Sid” # Error: cannot override “final” fieldJeśli metoda ma specyfikator final, nie można zastąpić tej metody w podklasie. W poniższym przykładzie klasa cat nie może zastąpić metody GetName(), ponieważ metoda ta ma specyfikator final.
pet := class():
Name : string
GetName<final>() : string = Name
cat := class(pet):
…
GetName<override>() : string = # Error: cannot override “final” method
…Wyrażenia block w sekcji body klasy
Możesz używać wyrażeń block w ciele klasy. Podczas tworzenia instancji klasy wyrażenia block są wykonywane w kolejności, w jakiej zostały zdefiniowane. Funkcje wywołane w wyrażeniach block w ciele klasy nie mogą mieć efektu NoRollback.
Przykładowo dodajmy dwa wyrażenia block do ciała klasy cat i dodajmy specyfikator efektu transacts do metody Meow(), ponieważ domyślny efekt dla metod ma efekt NoRollback.
cat := class():
Name : string
Age : int
Sound : string
Meow()<transacts> : void =
DisplayOnScreen(Sound)
block:
Self.Meow()
Po utworzeniu instancji klasy cat, OldCat, zostaną wykonane dwa wyrażenia block: kot najpierw powie „Rrrr”; wówczas w rejestrze wyjściowym zostanie wyświetlony komunikat „Garfield”.
Interfejsy
Interfejsy to ograniczona forma klas, która może zawierać tylko metody nieposiadające wartości. Klasy mogą dziedziczyć tylko z jednej innej klasy, ale mogą dziedziczyć z dowolnej liczby interfejsów.
Typ możliwy do persystencji
Klasa jest możliwa do utrwalenia, gdy:
Zdefiniowane za pomocą specyfikatora możliwego do utrwalenia.
Zdefiniowane za pomocą specyfikatora final, ponieważ klasy możliwe do utrwalenia nie mogą mieć podklas.
Nie unikatowe.
Nie ma nadklasy.
Nie parametryczne.
Zawiera tylko elementy członkowskie, które są również możliwe do utrwalenia.
Nie ma zmiennych jako elementów członkowskich.
Jeśli klasa jest persystentna, oznacza to, że możesz jej użyć w zmiennych weak_map o zasięgu modułowym i zachować ich wartości podczas sesji gry. Więcej informacji na temat persystencji w Verse znajdziesz w artykule Używanie persystentnych danych w Verse.
Poniższy przykład Verse pokazuje, jak można zdefiniować niestandardowy profil gracza w klasie, która może być przechowywana, aktualizowana i dostępna później dla gracza. Klasa player_profile_data przechowuje informacje o graczu, takie jak zdobyte PD, ranga i ukończone zadania.
player_profile_data := class<final><persistable>:
Version:int = 1
Class:player_class = player_class.Villager
XP:int = 0
Rank:int = 0
CompletedQuestCount:int = 0
QuestHistory:[]string = array{}