In Verse ist eine Klasse eine Vorlage zum Erstellen von Objekten mit ähnlichen Verhaltensweisen und Eigenschaften. Sie ist ein zusammengesetzter Typ, was bedeutet, dass es sich um gebündelte Daten anderer Typen und Funktionen handelt, die mit diesen Daten arbeiten können.
Klassen sind hierarchisch, was bedeutet, dass eine Klasse Informationen von ihren Parents (Superklasse) erben und ihre Informationen mit ihren Children (Subklassen) teilen kann. Klassen können ein vom Benutzer definierter benutzerdefinierter Typ sein. Vergleiche mit Instanz.
Nehmen wir zum Beispiel an, du möchtest mehrere Katzen in deinem Spiel haben. Eine Katze hat einen Namen und ein Alter, und sie kann miauen. Dann könnte deine Klasse „cat“ so aussehen:
cat := class:
Name : string
var Age : int = 0
Sound : string
Meow() : void = DisplayMessage(Sound)Definitionen von Variablen, die innerhalb der Klasse geschachtelt sind, definieren Felder der Klasse. Innerhalb einer Klasse definierte Funktionen können möglicherweise auch als Methoden bezeichnet werden. Felder und Methoden werden als Klassenmitglieder bezeichnet. Im obigen Beispiel ist Sound ein Feld und Meow ist eine Methode von cat.
Felder einer Klasse können einen Standardwert haben oder nicht, oder sie können nur einen Typ definieren, der die Werte, die das Feld haben kann, begrenzt. Im obigen Beispiel hat Name keinen Standardwert, Alter hingegen schon. Der Wert kann mit einem Ausdruck angegeben werden, der die <converges>-Effekte hat. Der Standardwertausdruck darf den Bezeichner Self nicht verwenden, was nachfolgend näher erläutert wird.
Nehmen wir zum Beispiel an, du möchtest, dass deine Katzen ihren Kopf schwenken können. Du kannst eine anfängliche Drehung HeadTilt im folgenden Code mit der Methode IdentityRotation() initialisieren, da sie den <converges>-Bezeichner hat und garantiert ohne Nebeneffekt abgeschlossen wird.
cat := class:
...
# A valid expression
HeadTilt:rotation = IdentityRotation()Konstruieren einer Klasse
Mithilfe einer Klasse, die definiert, was eine Katze ist und was sie tun kann, kannst du eine Instanz der Klasse aus einem Archetyp konstruieren. Ein Archetyp definiert die Werte der Klassenfelder. Zum Beispiel erstellen wir eine alte Katze namens Percy aus der Klasse cat:
OldCat := cat{Name := ”Percy”, Age := 20, Sound:= ”Rrrr”}In diesem Beispiel ist der Archetyp der Teil zwischen { and }. Sie muss nicht für alle Felder der Klasse Werte definieren, aber zumindest für alle Felder, die keinen Standardwert haben. Wenn ein Feld ausgelassen wird, hat die erstellte Instanz den Standardwert für dieses Feld.
In diesem Fall hat das Feld Age der Klasse cat einen Standardwert von zugewiesen (0). Da das Feld einen Standardwert hat, musst du bei der Erstellung einer Instanz der Klasse keinen Wert für das Feld angeben. Das Feld ist eine Variable, was bedeutet, dass du zwar bei der Erstellung einen Wert angeben kannst, der Wert dieser Variable aber nach der Konstruktion geändert werden kann.
Im Gegensatz dazu ist das Feld Name von cat keine veränderbare Variable und daher standardmäßig unveränderbar. Das bedeutet, dass du bei der Konstruktion einen Standardwert angeben kannst, der sich aber nach der Konstruktion nicht mehr ändern kann: Er ist unveränderlich.
Da eine Klasse in Verse eine Vorlage ist, kannst du so viele Instanzen der cat-Klasse erstellen, wie du möchtest. Lass uns ein Kätzchen namens Flash machen:
Kitten := cat{Name := ”Flash”, Age := 1, Sound := ”Mew”}Zugriff auf Felder
Nachdem du nun einige Instanzen von cat hast, kannst du auf das Feld Name jeder Katze mit OldCat.Name oder Kitten.Name zugreifen und die Meow-Methode jeder Katze mit OldCat.Meow() oder Kitten.Meow() aufrufen.
Beide Katzen haben die gleichen benannten Felder, aber diese Felder haben unterschiedliche Werte. Beispielsweise verhalten sich OldCat.Meow() und Kitten.Meow() unterschiedlich, weil ihre Sound-Felder unterschiedliche Werte haben.
Self
Self ist ein spezieller Bezeichner in Verse, die in einer Methode verwendet werden kann, um auf die Instanz der Klasse zu verweisen, für die die Methode aufgerufen wurde. Du kannst auf andere Felder der Instanz verweisen, auf der die Methode aufgerufen wurde, ohne Self zu verwenden, aber wenn du auf die Instanz als Ganzes verweisen möchtest, musst du Self verwenden.
Wenn beispielsweise DisplayMessage ein Argument dafür benötigt, welchem Haustier eine Nachricht zugeordnet werden soll:
DisplayMessage(Pet:pet, Message:string) : void = …
cat := class:
…
Meow() : void = DisplayMessage(Self, Sound)Wenn du eine lautere Version deines Katzenmiauens initialisieren möchtest, könntest du auf der Variablen Sound aufbauen, die du bereits eingerichtet hast. Dies wird im folgenden Code jedoch nicht funktionieren, da LoudSound nicht auf das Instanzmitglied Sound referenzieren kann, da Standardwertausdrücke nicht den Bezeichner Self verwenden können.
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)Subklassen und Vererbung
Klassen können von einer Superklasse erben, was alle Felder der Superklasse in der erbenden Klasse einschließt. Solche Klassen werden als Subklasse der Superklasse bezeichnet. Beispiel:
pet := class:
Name : string
var Age : int = 0
cat := class(pet):
Sound : string
Meow() : void = DisplayMessage(Self, Sound)
dog := class(pet):
Trick : string
Hier wird durch die Verwendung von class(pet) bei der Definition von cat und dog erklärt, dass sie von der Klasse pet erben. Mit anderen Worten: Sie sind Subklassen von pet.
Das hat mehrere Vorteile:
Da sowohl Katzen als auch Hunde Namen und Alter haben, müssen diese Felder nur einmal in der Klasse
petdefiniert werden. Sowohl das FeldKatzeals auch das FeldHunderben diese Felder.Die Klasse
petkann als Typ verwendet werden, um auf die Instanz einer beliebigen Subklasse vonpetzu referenzieren. Wenn du zum Beispiel eine Funktion schreiben möchtest, die nur den Namen eines Haustiers braucht, kannst du die Funktion einmal für Katzen und Hunde schreiben und für alle anderenpet-Subklassen, die du in Zukunft möglicherweise einführen wirst:VerseIncreaseAge(Pet : pet) : void= set Pet.Age += 1
Weitere Informationen findest du auf der Seite Subklasse.
Overrides
Wenn du eine Subklasse definierst, kannst du in der Oberklasse definierte Felder außer Kraft setzen, um ihren Typ zu präzisieren oder ihren Standardwert zu ändern. Dazu musst du die Definition des Feldes in deiner Subklasse erneut schreiben, aber mit dem Bezeichner <override> in seinem Namen. Du kannst zum Beispiel ein Feld Lives zu pet mit dem Standardwert 1 hinzufügen und den Standardwert für Katzen auf 9 setzen:
pet := class:
…
Lives : int = 1
cat := class(pet):
…
Lives<override> : int = 9Methodenaufrufe
Wenn du auf ein Feld einer Klasseninstanz zugreifst, greifst du auf den Wert des Feldes in dieser Instanz zu. Bei Methoden ist das Feld eine Funktion, deren außer Kraft setzen den Wert des Feldes durch eine neue Funktion ersetzt. Wenn du eine Methode aufrufst, wird der Wert des Feldes aufgerufen. Das bedeutet, dass durch die Instanz bestimmt wird, welche Methode aufgerufen wird. Betrachte das folgende Beispiel:
pet := class:
…
OnHearName() : void = {}
cat := class(pet):
…
OnHearName<override>() : void = Meow()
dog := class(pet):
…
Wenn du CallFor(Percy) schreibst, ruft die Methode OnHearName auf, die durch cat definiert ist. Wenn du CallFor(Fido) schreibst, wobei Fido eine Instanz der Klasse Hund ist, dann wird die Methode OnHearName wie von Hund definiert aufgerufen.
Sichtbarkeitsbezeichner
Du kannst zu Klassenfeldern und Methoden Sichtbarkeitsbezeichner hinzufügen, um zu steuern, wer Zugriff auf sie hat. So kannst du beispielsweise dem Feld Sound den Bezeichner private hinzufügen, so dass nur die besitzende Klasse auf dieses private Feld zugreifen kann.
cat := class:
…
Sound<private> : string
MrSnuffles := cat{Sound := "Purr"}
MrSnuffles.Sound # Error: cannot access a private fieldIm Folgenden sind alle Sichtbarkeitsbezeichner aufgeführt, die du mit Klassen verwenden kannst:
public: Uneingeschränkter Zugriff.
internal: Der Zugriff ist auf das aktuelle module begrenzt. Dies ist die Standard-Sichtbarkeit.
protected: Der Zugriff ist auf die aktuelle Klasse und alle Subklassen beschränkt.
private: Der Zugriff ist auf die aktuelle Klasse beschränkt.
Zugriffsbezeichner
Du kannst einer Klasse Zugriffsbezeichner hinzufügen, um zu kontrollieren, wer sie konstruieren darf. Dies ist z. B. nützlich, wenn du sicherstellen willst, dass eine Instanz einer Klasse nur in einem bestimmten Bereich konstruiert werden kann.
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 constructorDer Aufruf des Constructors für die Klasse cat außerhalb ihres Moduls pets wird fehlschlagen, da das Schlüsselwort class als intern gekennzeichnet ist. Dies ist True, obwohl der Klassenkennung selbst als öffentlich gekennzeichnet ist, was bedeutet, dass cat von Code außerhalb des Moduls pets referenziert werden kann.
Im Folgenden sind alle Zugriffsbezeichner aufgeführt, die du mit Klassen verwenden kannst:
public: Uneingeschränkter Zugriff. Dies ist die Standard-Zugang.
internal: Der Zugriff ist auf das aktuelle module begrenzt.
Concrete-Bezeichner
Wenn eine Klasse den Bezeichner concrete aufweist, ist es möglich, sie mit einem leeren Archetyp zu konstruieren, wie zum Beispiel cat{}. Das bedeutet, dass jedes Feld der Klasse einen Standardwert haben muss. Außerdem muss jede Subklasse einer konkreten Klasse selbst konkret sein.
Beispiel:
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 = 0Eine concrete-Klasse kann nur dann direkt von einer abstrakten Klasse erben, wenn beide Klassen in demselben Modul definiert sind. Es gilt jedoch nicht transitiv – eine concrete-Klasse kann direkt von einer zweiten concrete-Klasse in einem anderen Modul erben, wobei diese zweite concrete-Klasse direkt von einer abstract-Klasse in ihrem Modul erbt.
Unique-Bezeichner
Der Bezeichner unique kann auf eine Klasse angewendet werden, um sie zu einer eindeutigen Klasse zu machen. Um eine Instanz einer eindeutigen Klasse zu erstellen, weist Verse der entstehenden Instanz eine eindeutige Identität zu. So können Instanzen von eindeutigen Klassen auf Gleichheit verglichen werden, indem ihre Identitäten verglichen werden. Klassen ohne den unique-Bezeichner haben keine solche Identität und können daher nur auf der Grundlage der Werte ihrer Felder auf Gleichheit verglichen werden.
Das bedeutet, dass unique-Klassen mit den Operatoren = und <> verglichen werden können und Subtypen des Typs comparable sind.
Beispiel:
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 equalFinal-Bezeichner
Du kannst den Bezeichner final nur für Klassen und Felder von Klassen verwenden.
Wenn eine Klasse den Bezeichner final aufweist, kannst du keine Subklasse der Klasse erstellen. Im folgenden Beispiel kannst du die Klasse pet nicht als Superklasse verwenden, weil die Klasse den Bezeichner final hat.
pet := class<final>():
…
cat := class(pet): # Error: cannot subclass a “final” class
…Wenn ein Feld den Bezeichner final hat, kannst du das Feld nicht in einer Subklasse überschreiben. Im folgenden Beispiel kann die Klasse „cat“ das Feld Owner nicht überschreiben, weil das Feld den Bezeichner final hat.
pet := class():
Owner<final> : string = “Andy”
cat := class(pet):
Owner<override> : string = “Sid” # Error: cannot override “final” fieldWenn eine Methode den Bezeichner `final` hat, kannst du die Methode in einer Subklasse nicht außer Kraft setzen. Im folgenden Beispiel kann die Klasse „cat“ die Methode GetName() nicht überschreiben, weil die Methode den Bezeichner „final“ hat.
pet := class():
Name : string
GetName<final>() : string = Name
cat := class(pet):
…
GetName<override>() : string = # Error: cannot override “final” method
…Blockausdrücke in einem Klassenkörper
Du kannst Blockausdrücke in einem Klassenkörper verwenden. Wenn du eine Instanz der Klasse erstellst, werden die block-Ausdrücke in der Reihenfolge ausgeführt, in der sie definiert sind. Funktionen, die in block-Ausdrücken im Klassenkörper aufgerufen werden, können nicht den NoRollback-Effekt haben.
Als Beispiel fügen wir zwei block-Ausdrücke in den Klassenkörper cat ein und fügen der Methode Meow() den transacts-Effekt-Bezeichner hinzu, da der Standardeffekt für Methoden der Effekt „NoRollback“ ist.
cat := class():
Name : string
Age : int
Sound : string
Meow()<transacts> : void =
DisplayOnScreen(Sound)
block:
Self.Meow()
Wenn die Instanz der Klasse cat, OldCat, erstellt wird, werden die beiden block-Ausdrücke ausgeführt: Die Katze sagt zuerst „Rrrr“, dann wird „Garfield“ in das Output-Log geschrieben.
Interfaces
Interfaces sind eine begrenzte Form von Klassen, die nur Methoden enthalten können, die keinen Wert haben. Klassen nur von einer einzigen anderen Klasse erben, kann aber von einer beliebigen Anzahl von Interfaces erben.
Dauerhafter Typ
Eine Klasse ist in den folgenden Fällen dauerhaft:
Definiert mit dem Bezeichner „persistable“.
Sie ist mit dem Bezeichner final definiert, da dauerhafte Klassen keine Subklassen haben können.
Nicht unique.
Hat keine Superklasse.
Nicht parametrisch.
Enthält nur Mitglieder, die auch dauerhaft sind.
Hat keine Variablen-Mitglieder.
Wenn eine Klasse „persistable“ ist, bedeutet das, dass du sie in deinen Modul-weiten weak_map-Variablen verwenden kannst und ihre Werte über Spielsitzungen hinweg erhalten bleiben. Für weitere Einzelheiten zur Persistenz in Verse, schau dir Using Persistable Data in Verse an.
Das folgende Verse-Beispiel zeigt, wie du ein benutzerdefiniertes Spielerprofil in einer Klasse definieren kannst, das gespeichert, aktualisiert und später für einen Spieler aufgerufen werden kann. Die Klasse player_profile_data speichert Informationen für einen Spieler, wie die verdienten EP, seinen Rang und die Aufträge, die er abgeschlossen hat.
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{}