In Verse, una classe è un modello per la creazione di oggetti con comportamenti e proprietà simili. È un tipo composito, il che significa che contiene dati raggruppati di altri tipi e funzioni che possono operare su quei dati.
Le classi sono gerarchiche, il che significa che una classe può ereditare le informazioni dal padre (superclasse) e condividere le sue informazioni con i figli (sottoclassi). Le classi possono essere di tipi personalizzati definiti dall'utente. Confronta con istanza.
Ad esempio, supponiamo che tu voglia avere più gatti nel tuo gioco. Un gatto ha un nome e un'età e può miagolare. La classe di gatto potrebbe assomigliare a questa:
cat := class:
Name : string
var Age : int = 0
Sound : string
Meow() : void = DisplayMessage(Sound)Le definizioni delle variabili che sono nidificate all'interno della classe definiscono campi della classe. Le funzioni definite all'interno di una classe possono anche essere chiamate metodi. I campi e i metodi sono indicati come membri della classe. Nell'esempio precedente, Sound è un Campo e Meow è un metodo di cat.
I campi di una classe possono avere o meno un valore predefinito, oppure possono definire solo un tipo che limita i valori che può avere il campo. Nell'esempio precedente, Name non ha un valore predefinito mentre Age lo ha. Il valore può essere specificato utilizzando un'espressione che ha gli effetti<converges>. L'espressione di valore predefinita non può utilizzare l'identificatore Self, che verrà spiegato più avanti.
Per esempio, poniamo che vuoi che i tuoi gatti possano inclinare la testa. Puoi inizializzare una rotazione iniziale HeadTilt nel codice seguente utilizzando il metodo IdentityRotation() perché ha l'identificatore <converges> ed è garantito il completamento senza effetti collaterali.
cat := class:
...
# A valid expression
HeadTilt:rotation = IdentityRotation()Costruzione di una classe
Con una classe che definisce che cosa è un gatto e cosa può fare il gatto, puoi costruire un'istanza della classe da un archetipo. Un archetipo definisce i valori dei campi di una classe. Ad esempio, creiamo un vecchio gatto di nome Percy dalla classe cat:
OldCat := cat{Name := ”Percy”, Age := 20, Sound:= ”Rrrr”}In questo esempio, l'archetipo è la parte compresa tra { and }. Non è necessario che definisca i valori per tutti i campi della classe, ma deve almeno definire valori per tutti i campi che non hanno un valore predefinito. Se un campo viene omesso, l'istanza costruita avrà il valore predefinito per quel campo.
In questo caso, al campo Age della classe cat è assegnato un valore predefinito di (0). Poiché il campo ha un valore predefinito, non è necessario fornire un valore per esso quando si costruisce un'istanza della classe. Il campo è una variabile, il che significa che, sebbene sia possibile fornire un valore in fase di costruzione, il valore di tale variabile può essere modificato dopo la costruzione.
Al contrario, il campo Name di cat non è una variabile modificabile e quindi non è modificabile per impostazione predefinita. Ciò significa che è possibile fornire un valore predefinito al momento della costruzione, ma dopo la costruzione non può cambiare: è non modificabile.
Poiché una classe in Verse è un modello, puoi creare tutte le istanze che vuoi dalla classe cat. Creiamo ora ad esempio un gattino di nome Flash:
Kitten := cat{Name := ”Flash”, Age := 1, Sound := ”Mew”}Accesso ai campi
Ora che hai alcune istanze di cat, puoi accedere al campo Name di ogni gatto con OldCat.Name o Kitten.Name e chiamare il metodo Meow di ogni gatto con OldCat.Meow() o Kitten.Meow().
Entrambi i gatti hanno gli stessi campi denominati, ma questi campi hanno valori diversi. Ad esempio, OldCat.Meow() e Kitten.Meow() si comportano in modo diverso perché i loro campi Sound hanno valori diversi.
Se stesso
Self è un identificatore speciale di Verse che si può utilizzare in un metodo di classe per fare riferimento all'istanza della classe su cui il metodo è stato chiamato. È possibile fare riferimento ad altri campi dell'istanza da cui è stato chiamato il metodo senza utilizzare Self, ma se si vuole fare riferimento all'istanza nel suo complesso, è necessario utilizzare Self.
Ad esempio, se DisplayMessage richiede un argomento per quale animale domestico associare un messaggio:
DisplayMessage(Pet:pet, Message:string) : void = …
cat := class:
…
Meow() : void = DisplayMessage(Self, Sound)Se volevi inizializzare una versione più forte del miagolio dei tuoi gatti, puoi pensare di basarti sulla variabile Sound che hai già impostato. Questo però non funzionerà nel codice seguente, perché LoudSound non può fare riferimento al membro di istanza Sound, dato che le espressioni di valore predefinito non possono utilizzare l'identificatore 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)Sottoclassi ed ereditarietà
Le classi possono ereditare da una superclasse, che include tutti i campi della superclasse nella classe di eredità. Tali classi sono dette sottoclasse della superclasse. Ad esempio:
pet := class:
Name : string
var Age : int = 0
cat := class(pet):
Sound : string
Meow() : void = DisplayMessage(Self, Sound)
dog := class(pet):
Trick : string
In questo caso, l'uso di class(pet) quando si definiscono cat e dog dichiara che ereditano dalla classe pet. In altre parole, sono sottoclassi di pet.
Questo ha diversi vantaggi:
Poiché sia i cani che i gatti hanno nomi ed età, tali campi devono essere definiti una sola volta nella classe
pet. Sia il campocatche il campodogereditano tali campi.La classe
petsi può utilizzare come tipo per fare riferimento all'istanza di qualsiasi sottoclasse dipet. Ad esempio, se si desidera scrivere una funzione che richiede solo il nome di un animale domestico, è possibile scrivere la funzione una sola volta sia per cani che per gatti e qualsiasi altra sottoclassepetche potrebbe essere introdotta in futuro:VerseIncreaseAge(Pet : pet) : void= set Pet.Age += 1
Per maggiori informazioni, vedi la pagina della sottoclasse.
Override
Quando si definisce una sottoclasse, è possibile eseguire la sostituzione dei campi definiti nella superclasse per renderne più specifico il tipo o modificarne il valore predefinito. Per farlo, devi scrivere di nuovo la definizione del campo nella tua sottoclasse, ma con lo specificatore <override> sul nome. Ad esempio, è possibile aggiungere un campo Lives a pet con un valore predefinito di 1 e ignorare il valore predefinito per i gatti che è 9:
pet := class:
…
Lives : int = 1
cat := class(pet):
…
Lives<override> : int = 9Chiamate del metodo
Quando si accede a un campo di un'istanza di classe, si accede al valore di tale istanza per il campo. Per i metodi, il campo è una funzione e la sostituzione di esso sostituisce il valore del campo con una nuova funzione. La chiamata di un metodo invoca il valore del campo. Ciò significa che il metodo chiamato è determinato dall'istanza. Considera l'esempio seguente:
pet := class:
…
OnHearName() : void = {}
cat := class(pet):
…
OnHearName<override>() : void = Meow()
dog := class(pet):
…
Se scrivi CallFor(Percy), viene chiamato il metodo OnHearName come definito da cat. Se scrivi CallFor(Fido) dove Fido è un'istanza della classe dog, viene chiamato il metodo OnHearName come definito da dog.
Specificatori di visibilità
Puoi aggiungere specificatori di visibilità ai campi della classe e ai metodi per controllare chi vi ha accesso. Ad esempio, è possibile aggiungere lo specificatore private al campo Sound in modo che soltanto la classe proprietaria possa accedere a tale campo privato.
cat := class:
…
Sound<private> : string
MrSnuffles := cat{Sound := "Purr"}
MrSnuffles.Sound # Error: cannot access a private fieldDi seguito sono riportati tutti gli specificatori di visibilità che si possono utilizzare con le classi:
public: accesso illimitato.
internal: accesso limitato al modulo corrente. Questa è la visibilità predefinita.
protected: accesso limitato alla classe corrente e a qualsiasi sottoclasse.
private: accesso limitato alla classe corrente.
Specificatori di accesso
Puoi aggiungere specificatori di accesso a una classe per controllare chi può costruirla. Ciò risulta utile, ad esempio, se vuoi fare in modo che un'istanza di una classe possa essere costruita solo con un determinato scopo.
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 constructorLa chiamata del costruttore per la classe cat al di fuori del suo modulo pets non riesce perché la parola chiave class è contrassegnata come interna. Questo è vero anche se l'identificatore di classe stesso è contrassegnato come pubblico, il che significa che cat può essere riferito dal codice al di fuori del modulo pets.
Di seguito sono riportati tutti gli specificatori di accesso che è possibile utilizzare con la parola chiave della classe:
public: accesso illimitato. Questo è l'accesso predefinito.
interno: accesso limitato al modulo corrente.
Specificatore Concrete
Quando una classe ha lo specificatore concrete, è possibile costruirla con un archetipo vuoto, come ad esempio cat{}. Ciò significa che ogni campo della classe deve avere un valore predefinito. Inoltre, ogni sottoclasse di una classe concreta deve essere essa stessa concreta.
Ad esempio:
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 = 0Una classe concrete può ereditare direttamente da una classe abstract soltanto se entrambe le classi sono definite nello stesso modulo. Tuttavia, non vale in modo transitivo: una classe concrete può ereditare direttamente da una seconda classe concrete in un altro modulo in cui quella seconda classe concrete eredita direttamente da una classe abstract nel suo modulo.
Specificatore Unique
Lo specificatore unique può essere applicato a una classe per renderla univoca. Per costruire un'istanza di una classe univoca, Verse alloca un'identità univoca per l'istanza risultante. Ciò consente di verificare l'uguaglianza tra istanze di classi univoche confrontando le loro identità con gli operatori =, < e >, il che le rende sottotipi del tipo confrontable. Le classi prive dello specificatore unique non hanno tale identità e quindi si possono confrontare solo per l'uguaglianza in base ai valori dei loro campi.
Ciò significa che le classi univoche si possono confrontare con gli operatori = e <> e sono sottotipi del tipo confrontabile.
Ad esempio:
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 equalSpecificatore Final
È possibile utilizzare lo specificatore final soltanto su classi e campi di classi.
Quando una classe ha lo specificatore final, non è possibile creare una sottoclasse della classe. Nell'esempio seguente, non è possibile utilizzare la classe pet come superclasse perché la classe ha lo specificatore final.
pet := class<final>():
…
cat := class(pet): # Error: cannot subclass a “final” class
…Quando un campo ha lo specificatore final, non è possibile eseguire l'override del campo in una sottoclasse. Nell'esempio seguente, la class cat non può eseguire l'override del campo Owner, perché il campo ha lo specificatore final.
pet := class():
Owner<final> : string = “Andy”
cat := class(pet):
Owner<override> : string = “Sid” # Error: cannot override “final” fieldQuando un metodo dispone dello specificatore final, non è possibile eseguire l'override del metodo in una sottoclasse. Nell'esempio seguente, la classe cat non può eseguire l'override del metodo GetName() perché tale metodo ha lo specificatore final.
pet := class():
Name : string
GetName<final>() : string = Name
cat := class(pet):
…
GetName<override>() : string = # Error: cannot override “final” method
…Espressioni di blocco nel corpo di una classe
È possibile utilizzare espressioni di blocco in corpo di una classe. Quando crei un'istanza della classe, le espressioni block vengono eseguite nell'ordine in cui sono definite. Le funzioni chiamate nelle espressioni block nel corpo della classe non possono avere l'effetto NoRollback.
Ad esempio, aggiungiamo due espressioni block al corpo della classe cat e aggiungiamo lo specificatore effetto transacts al metodo Meow() perché l'effetto predefinito dei metodi è NoRollback.
cat := class():
Name : string
Age : int
Sound : string
Meow()<transacts> : void =
DisplayOnScreen(Sound)
block:
Self.Meow()
Quando viene creata l'istanza della classe cat, OldCat, vengono eseguite le due espressioni block: il gatto dirà prima "Rrrr"; quindi "Garfield" verrà stampato nel registro di output.
Interfacce
Le interfacce sono una forma limitata di classi che possono contenere solo metodi sprovvisti di un valore. Le classi possono ereditare soltanto da una singola altra classe, ma possono ereditare da un numero qualsiasi di interfacce.
Tipo persistente
Una classe è persistente quando è:
Definita con lo specificatore persistente.
Definita con lo specificatore finale, perché le classi persistenti non possono avere sottoclassi.
Non univoca.
Non ha una superclasse.
Non parametrica.
Contiene solo membri che sono anche persistenti.
Non ha membri variabili.
Quando una classe è persistente, significa che puoi utilizzarle nelle variabili weak_map con ambito modulo e che i loro valori persistono nelle varie sessioni di gioco. Per maggiori dettagli sulla persistenza in Verse, consulta Utilizzo dei dati persistenti in Verse.
Il seguente esempio di Verse mostra come puoi definire un profilo giocatore personalizzato in una classe che può essere archiviata, aggiornata e accessibile in seguito da parte del giocatore. La classe player_profile_data archivia le informazioni del giocatore, come i PE ottenuti, la posizione in classifica e le missioni che ha completato.
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{}