In Verse, a class is a template for creating objects with similar behaviors and properties. It is a composite type, which means that it’s bundled data of other types and functions that can operate on that data.
For example, let’s say you want to have multiple cats in your game. A cat has a name and an age, and they can meow. Your cat class could look like this:
cat := class:
Name : string
var Age : int = 0
Sound : string
Meow() : void = DisplayMessage(Sound)
Definitions of variables that are nested inside the class define fields of the class. Functions defined inside a class may also be called methods. Functions and methods are referred to as class members. In the above example, Sound
is a field, and Meow
is a method of cat
.
Fields of a class may or may not have a default value, or may only define a type that limits the values that the field may have. In the above example, Name does not have a default value, while Age does. The value may be specified using an expression that has the
For instance, lets say you want your cats to be able to tilt their heads. You can initialize an initial rotation HeadTilt
in the following code using the IdentityRotation()
method because it has the <converges>
specifier and is guaranteed to complete with no side effects.
cat := class:
...
# Prawidłowe wyrażenie
HeadTilt:rotation = IdentityRotation()
Konstruowanie klasy
With a class that defines what a cat is and what the cat can do, you can construct an instance of the class from an archetype. An archetype defines the values of the class fields. For example, let’s make an old cat named Percy from the cat
class:
OldCat := cat{Name := â€Percyâ€, Age := 20, Sound:= â€Rrrrâ€}
W tym przykładzie archetypem jest część pomiędzy {
i }
. 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.
In this case, the cat
class Age
field has a default value assigned to it of (0
). Since the field has a default value, you’re not required to provide a value for it when constructing an instance of the class. The field is a variable, which means that though you might provide a value at construction time, the value of that variable can be changed after construction.
Z kolei pole Name
klasy cat
nie jest modyfikowalną zmienną, 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.
Since a class in Verse is a template, you can make as many instances as you want from the cat
class. Let’s make a kitten named Flash:
Kitten := cat{Name := â€Flashâ€, Age := 1, Sound := â€Mewâ€}
Uzyskiwanie dostępu do pól
Now that you have some instances of cat
, you can access each cat’s Name
field with OldCat.Name
or Kitten.Name
, and call each cat's Meow
method with OldCat.Meow()
or 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 Sound
mają różne wartości.
Identyfikator Self
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żesz odwoływać się bez użycia identyfikatora Self
, ale jeśli chcesz odwołać się do instancji jako całości, musisz użyć 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)
# Poniższe nie powiedzie się, ponieważ domyślne wartości klas
# nie mogą odwoływać się do Self
LoudSound : string = "Loud " + Self.Sound
LoudMeow() : void = DisplayMessage(Self, LoudSound)
Podklasy i dziedziczenie
Klasy mogą dziedziczyć z nadklasy, która zawiera 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
DoTrick() : void = DisplayMessage(Self, Trick)
Użycie class(pet)
podczas definiowania klas cat
i dog
oznacza, że dziedziczą one z klasy pet
. Innymi słowy, są one podklasami klasy pet
.
Ma to kilka zalet:
- Since both cats and dogs have names and ages, those fields only have to be defined once, in the
pet
class. Both thecat
and thedog
field will inherit those fields. - The
pet
class can be used as a type to refer to instance of any subclass ofpet
. For example, if you want to write a function that just needs the name of a pet, you can write the function once for both cats and dogs, and any otherpet
subclasses you might introduce in the future:IncreaseAge(Pet : pet) : void= set Pet.Age += 1
Aby dowiedzieć się więcej, patrz Podklasa.
Zastąpienia
When you define a subclass, you can override fields defined in the superclass to make their type more specific, or change their default value. To do so, you must write the definition of the field in your subclass again, but with the <override>
specifier on its name. For example, you can add a Lives
field to pet
with a default value of 1, and override the default value for cats to be 9:
pet := class:
…
Lives : int = 1
cat := class(pet):
…
Lives<override> : int = 9
Wywoł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):
…
OnHearName<override>() : void = DoTrick()
CallFor(Pet:pet):void=
DisplayMessage("Dobra robota, {Pet.Name}!")
Pet.OnHearName()
Jeśli napiszesz CallFor(Percy)
, wywoła to metodę OnHearName
zdefiniowaną 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 := "Mrrrau"}
MrSnuffles.Sound # Błąd: Nie można uzyskać dostępu do prywatnego pola
Oto wszystkie specyfikatory widoczności, których można używać z klasami:
- public: Unrestricted access.
- internal: Access limited to current module. This is the default visibility.
- protected: Access limited to current class and any subclasses.
- private: Access limited to current class.
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 constructor
Wywoł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: Unrestricted access. This is the default access.
- internal: Access limited to the current module.
Specyfikator concrete
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 = 0
A concrete
class can only inherit directly from an abstract
class if both classes are defined in the same module. However, it does not hold transitively — a concrete
class can inherit directly from a second concrete
class in another module where that second concrete
class inherits directly from an abstract
class in its module.
Specyfikator unique
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 jest równe samemu sobie
Y := unique_class{Field := 1}
X <> Y # X i Y są unikatowe, a więc nie są równe
Specyfikator final
Specyfikatora final
można używać tylko w przypadku klas i pól 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
…
When a field has the final
specifier, you cannot override the field in a subclass. In the following example, the cat class can’t override the Owner
field, because the field has the final
specifier.
pet := class():
Owner<final> : string = “Andyâ€
cat := class(pet):
Owner<override> : string = “Sid†# Error: cannot override “final†field
When a method has the final specifier, you cannot override the method in a subclass. In the following example, the cat class can’t override the GetName()
method, because the method has the final specifier.
pet := class():
Name : string
GetName<final>() : string = Name
cat := class(pet):
…
GetName<override>() : string = # Error: cannot override “final†method
…
Wyrażenia block w ciele klasy
W ciele klasy można używać wyrażeń block. 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.
As an example, let’s add two block
expressions to the cat
class body and add the transacts effect specifier to the Meow()
method because the default effect for methods has the NoRollback effect.
cat := class():
Name : string
Age : int
Sound : string
Meow()<transacts> : void =
DisplayOnScreen(Sound)
block:
Self.Meow()
block:
Log(Self.Name)
OldCat := cat{Name := "Garfield", Age := 20, Sound := "Rrrr"}
When the instance of the cat
class, OldCat
, is created, the two block
expressions are executed: the cat will first say “Rrrrâ€; then “Garfield†will print to the output log.
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.