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:
...
# Un'espressione valida
HeadTilt:rotation = IdentityRotation()
Costruzione di una classe
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â€}
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 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.
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.
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â€}
Accesso ai campi
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()
.
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.
Self
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 su cui è stato chiamato il metodo senza utilizzare Self
, ma se si desidera fare riferimento all'istanza nel suo complesso, è necessario utilizzare Self
.
Ad esempio, se DisplayMessage
richiedesse un argomento per quale animale domestico associare un messaggio a:
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)
# Quanto segue avrà esito negativo a causa dei valori predefiniti delle classi
# non può fare riferimento a 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
DoTrick() : void = DisplayMessage(Self, Trick)
In questo caso, l'utilizzo 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:
- 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
Per maggiori informazioni, vedi la pagina Sottoclasse.
Sostituzioni
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
Chiamate 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):
…
OnHearName<override>() : void = DoTrick()
CallFor(Pet:pet):void=
DisplayMessage("Ehilà {Pet.Name}!")
Pet.OnHearName()
Se scrivi CallFor(Percy)
, verrà chiamato il metodo OnHearName
come definito da cat
. Se scrivi CallFor(Fido)
dove Fido
è un'stanza della classe dog
, verrà chiamato il metodo OnHearName
come definito da dog
.
Specificatori di visibilità
È possibile 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 field
Di seguito sono riportati tutti gli specificatori di visibilità che si possono utilizzare con le classi:
- 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.
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 constructor
La chiamata del costruttore per la classe cat
al di fuori del modulo pets
non va a buon fine, perché la parola chiave class
è contrassegnata come interna. Questo è vero nonostante l'identificativo di classe stesso sia 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: Unrestricted access. This is the default access.
- internal: Access limited to the current module.
Specificatore concrete
Quando una classe ha lo specificatore concrete
, è possibile costruirlo con un archetipo vuoto, come 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 = 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.
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 senza lo 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 uniche si possono confrontare con gli operatori =
e <>
e sono sottotipi del tipo comparable
.
Ad esempio:
unique_class := class<unique>:
Field : int
Main()<decides> : void =
X := unique_class{Field := 1}
X = X # X è uguale a se stesso
Y := unique_class{Field := 1}
X <> Y # X e Y sono univoci e quindi non sono uguali
Specificatore 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
…
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
…
Espressioni di blocco nel corpo di una classe
Puoi utilizzare le espressioni di blocco in un corpo della classe. Quando si crea 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.
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.
Interfacce
Le interfacce sono una forma limitata di classi che possono contenere soltanto metodi che non hanno un valore. Le classi possono ereditare soltanto da una singola altra classe, ma possono ereditare da un numero qualsiasi di interfacce.