Dans Verse, une classe est un modèle permettant de créer des objets aux comportements et aux propriétés semblables. Il s'agit d'un type composite, c'est-à-dire qu'il regroupe des données d'autres types et fonctions pouvant opérer sur ces données.
Les classes sont hiérarchiques, ce qui signifie qu'une classe peut hériter des informations de son parent (super-classe) et partager ses informations avec ses enfants (sous-classes). Les classes peuvent être un type personnalisé défini par l'utilisateur. Comparez cette définition à celle du terme instance.
Par exemple, disons que vous voulez avoir plusieurs chats dans votre jeu. Un chat a un nom et un âge, et il peut miauler. Votre classe de chat pourrait ressembler à ça :
cat := class:
Name : string
var Age : int = 0
Sound : string
Meow() : void = DisplayMessage(Sound)Les définitions des variables imbriquées dans la classe définissent les champs de la classe. Les fonctions définies dans une classe peuvent également être appelées méthodes. Les champs et les méthodes sont appelés membres de classe. Dans l'exemple ci-dessus, Sound est un champ et Meow est une méthode de cat.
Les champs d'une classe peuvent avoir ou non une valeur par défaut, ou bien définir uniquement un type qui limite les valeurs que le champ peut avoir. Dans l'exemple ci-dessus, "Name" n'a pas de valeur par défaut, alors qu'"Age" en a une. La valeur peut être spécifiée à l'aide d'une expression disposant des effets<converges>. Comme nous allons vous l'expliquer plus bas, l'expression de valeur par défaut ne peut utiliser l'identificateur Self.
Par exemple, disons que vous souhaitez que vos chats puissent incliner leur tête. Vous pouvez initialiser une rotation initiale HeadTilt dans le code suivant à l'aide de la méthode IdentityRotation(), car elle dispose du spécificateur <converges> et car son exécution sans effet indésirable est garantie.
cat := class:
...
# A valid expression
HeadTilt:rotation = IdentityRotation()Construire une classe
Avec une classe qui définit ce qu'est un chat et ce qu'il peut faire, vous pouvez construire une instance de la classe à partir d'un archétype. Un archétype définit les valeurs des champs de la classe. Par exemple, créons un vieux chat nommé Percy à partir de la classe cat :
OldCat := cat{Name := ”Percy”, Age := 20, Sound:= ”Rrrr”}Dans cet exemple, l'archétype est la partie située entre { and }. Il n'est pas nécessaire de définir des valeurs pour tous les champs de la classe, mais il faut au moins définir des valeurs pour tous les champs qui n'ont pas de valeur par défaut. Si un champ est omis,l'instance construite aura la valeur par défaut pour ce champ.
Dans ce cas, le champ Âge de la classe cat se voit attribuer une valeur par défaut de (0). Puisque le champ a une valeur par défaut, vous n'êtes pas obligé de lui fournir une valeur lors de la construction d'une instance de la classe. Le champ est une variable, ce qui signifie que même si vous fournissez une valeur au moment de la construction, la valeur de cette variable peut être modifiée après la construction.
En revanche, le champ Name de cat n'est pas une variable mutable et est donc immuable par défaut. Cela signifie que vous pouvez lui donner une valeur par défaut au moment de la construction, mais qu'après la construction, elle ne peut plus changer : elle est immuable.
Une classe étant un modèle dans Verse, vous pouvez créer autant d'instances que vous voulez de la classe cat. Créons un chaton qui s'appelle Flash :
Kitten := cat{Name := ”Flash”, Age := 1, Sound := ”Mew”}Accéder aux champs
Maintenant que vous avez quelques instances de cat, vous pouvez accéder au champ Name de chaque chat avec OldCat.Name ou Kitten.Name et appeler la méthode Meow de chaque chat avec OldCat.Meow() ou Kitten.Meow().
Les deux chats ont les mêmes champs de nom, mais ces champs ont des valeurs différentes. Par exemple, OldCat.Meow() et Kitten.Meow() se comportent différemment parce que leurs champs Sound ont des valeurs différentes.
Self
Self est un identificateur spécial dans Verse qui peut être utilisé dans une méthode de classe pour faire référence à l'instance de la classe sur laquelle la méthode a été appelée. Vous pouvez faire référence à d'autres champs de l'instance sur laquelle la méthode a été appelée sans utiliser Self, mais si vous souhaitez faire référence à l'instance dans son ensemble, vous devez utiliser Self.
Par exemple, si DisplayMessage nécessitait un argument pour savoir à quel animal associer un message :
DisplayMessage(Pet:pet, Message:string) : void = …
cat := class:
…
Meow() : void = DisplayMessage(Self, Sound)Si vous souhaitiez initialiser une version plus audible du miaulement de vos chats, vous pourriez penser pouvoir vous baser sur la variable Sound que vous avez déjà configurée. Toutefois, cela ne fonctionnera pas dans le code suivant, LoudSound ne pouvant référencer le membre d'instance Sound puisque les expressions de valeur par défaut ne peuvent pas utiliser l'identificateur 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)Sous-classes et héritage
Les classes peuvent hériter d'une superclasse, qui inclut tous les champs de la super-classe dans la classe qui en hérite. Ces classes sont considérées comme des sous-classes de la super-classe. Par exemple :
pet := class:
Name : string
var Age : int = 0
cat := class(pet):
Sound : string
Meow() : void = DisplayMessage(Self, Sound)
dog := class(pet):
Trick : string
Ici, l'utilisation de class(pet) lors de la définition de cat et dog déclare qu'ils héritent de la classe pet. En d'autres termes, ce sont des sous-classes de pet.
Cela présente plusieurs avantages :
Comme les chats et les chiens ont tous un nom et un âge, ces champs ne doivent être définis qu'une seule fois, dans la classe
pet. Les champscatetdoghériteront tous deux de ces champs.La classe
petpeut être utilisée comme type pour faire référence à une instance de n'importe quelle sous-classe depet. Par exemple, si vous voulez rédiger une fonction qui a juste besoin du nom d'un animal de compagnie, vous pouvez écrire la fonction une fois pour les chats et les chiens et pour toute autre sous-classepetque vous pourriez mettre en place à l'avenir :VerseIncreaseAge(Pet : pet) : void= set Pet.Age += 1
Pour plus d'informations, consultez la page Sous-classe.
Remplacements
Lorsque vous définissez une sous-classe, vous pouvez remplacer les champs définis dans la superclasse pour préciser leur type ou modifier leur valeur par défaut. Pour ce faire, vous devez réécrire la définition du champ dans votre sous-classe, mais avec le spécificateur <override> sur son nom. Par exemple, vous pouvez ajouter à pet un champ Lives dont la valeur par défaut est 1 et remplacer la valeur par défaut des chats par 9 :
pet := class:
…
Lives : int = 1
cat := class(pet):
…
Lives<override> : int = 9Appels de méthode
Lorsque vous accédez à un champ d'une instance de classe, vous accédez à la valeur du champ de cette instance. Pour les méthodes, le champ est une fonction, et le spécificateur `` remplace la valeur du champ par une nouvelle fonction. L'appel d'une méthode appelle la valeur du champ. Cela signifie que la méthode appelée est déterminée par l'instance. Prenons l'exemple suivant :
pet := class:
…
OnHearName() : void = {}
cat := class(pet):
…
OnHearName<override>() : void = Meow()
dog := class(pet):
…
Si vous écrivez CallFor(Percy), la méthode OnHearName, définie par cat, sera appelée. Si vous écrivez CallFor(Fido) où Fido est une instance de la classe dog, alors la méthode OnHearName, définie par dog, sera appelée.
Spécificateurs de visibilité
Vous pouvez ajouter des spécificateurs de visibilité aux champs et méthodes des classes pour contrôler qui y a accès. Par exemple, vous pouvez ajouter le spécificateur private au champ Sound pour que seule la classe propriétaire puisse accéder à ce champ privé.
cat := class:
…
Sound<private> : string
MrSnuffles := cat{Sound := "Purr"}
MrSnuffles.Sound # Error: cannot access a private fieldVoici tous les spécificateurs de visibilité que vous pouvez utiliser avec les classes :
public : accès sans restriction.
interne : accès limité au module actuel. Il s'agit de la visibilité par défaut.
protégé : accès limité à la classe actuelle et à toute sous-classe.
privé : accès limité à la classe actuelle.
Spécificateurs d'accès
Vous pouvez ajouter des spécificateurs d'accès à une classe pour contrôler qui peut utiliser cette dernière. Cette fonction est utile, notamment pour vous assurer qu'une instance d'une classe ne peut être construite que selon une certaine étendue.
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 constructorL'appel du constructeur de la classe cat en dehors de son module pets échoue, car le mot-clé class est marqué comme interne. Cela est vrai même si l'identificateur de la classe proprement dit est marqué comme public, ce qui signifie qu'il est possible de référencer cat avec du code en dehors du module pets.
Voici tous les spécificateurs d'accès que vous pouvez utiliser avec le mot-clé `class` :
public : accès sans restriction. Il s'agit de l'accès par défaut.
interne : accès limité au module actuel.
Spécificateur "concrete"
Lorsqu'une classe possède le spécificateur concrete, il est possible de la construire avec un archétype vide, tel que cat{}. Cela signifie que chaque champ de la classe doit avoir une valeur par défaut. En outre, toute sous-classe d'une classe concrète doit elle-même être concrète.
Par exemple :
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 = 0Une classe concrete ne peut hériter directement d'une classe abstract que si les deux classes sont définies dans le même module. Cependant, une classe concrete peut hériter directement d'une seconde classe concrete d'un autre module lorsque cette seconde classe concrete hérite directement d'une classe abstract de son module.
Spécificateur `unique`
Le spécificateur unique peut s'appliquer à une classe pour en faire une classe unique. Pour construire une instance d'une classe unique, Verse alloue une identité unique à l'instance qui en résulte. Cela permet aux instances de classes uniques d'être comparées en matière d'égalité selon leurs identités. Les classes sans spécificateur unique n'ont pas d'identité de ce type, leur égalité n'est donc comparable que selon leurs valeurs de champs.
Cela signifie que les classes `unique` peuvent être comparées avec les opérateurs = et <>, et sont des sous-types du type comparable.
Par exemple :
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 equalSpécificateur `final`
Vous ne pouvez utiliser le spécificateur final que sur les classes et les champs des classes.
Lorsqu'une classe possède le spécificateur final, vous ne pouvez pas créer de sous-classe de cette classe. Dans l'exemple suivant, vous ne pouvez pas utiliser la classe pet comme super-classe, car la classe possède le spécificateur final.
pet := class<final>():
…
cat := class(pet): # Error: cannot subclass a “final” class
…Lorsqu'un champ possède le spécificateur final, vous ne pouvez pas remplacer le champ dans une sous-classe. Dans l'exemple suivant, la classe cat ne peut pas remplacer le champ Owner, car ce dernier possède le spécificateur final.
pet := class():
Owner<final> : string = “Andy”
cat := class(pet):
Owner<override> : string = “Sid” # Error: cannot override “final” fieldLorsqu'une méthode possède le spécificateur `final`, vous ne pouvez pas remplacer la méthode dans une sous-classe. Dans l'exemple suivant, la classe cat ne peut pas remplacer la méthode GetName(), car la méthode possède le spécificateur final.
pet := class():
Name : string
GetName<final>() : string = Name
cat := class(pet):
…
GetName<override>() : string = # Error: cannot override “final” method
…Expressions de bloc dans le corps d'une classe
Vous pouvez utiliser des expressions de bloc dans le corps d'une classe. Lorsque vous créez une instance de la classe, les expressions block sont exécutées dans l'ordre où elles ont été définies. Les fonctions appelées dans des expressions block dans le corps de la classe ne peuvent pas avoir l'effet NoRollback.
Par exemple, ajoutons deux expressions block au corps de la classe cat et ajoutons le spécificateur d' effet transacts à la méthode Meow(), car l'effet par défaut des méthodes possède l'effet NoRollback.
cat := class():
Name : string
Age : int
Sound : string
Meow()<transacts> : void =
DisplayOnScreen(Sound)
block:
Self.Meow()
Lorsque l'instance OldCat de la classe cat est créée, les deux expressions block sont exécutées : le chat dira d'abord "Rrrr", puis "Garfield" s'affichera dans le journal de sortie.
Interfaces
Les interfaces sont une forme limitée de classes qui ne peuvent contenir que des méthodes qui n'ont pas de valeur. Les classes ne peuvent hériter que d'une seule autre classe, mais peuvent hériter d'un nombre quelconque d'interfaces.
Type persistable
Une classe est persistante dans les cas suivants :
Défini avec le spécificateur persistable.
Définie avec le spécificateur final, car les classes persistables ne peuvent pas avoir de sous-classes.
Non unique.
N'a pas de super-classe.
Non paramétrique.
Contient uniquement les membres qui sont également persistables.
N'a pas de membres de variable.
Cela signifie que vous pouvez utiliser une classe capable de persister dans vos variables weak_map à étendue de module et faire persister leurs valeurs d'une session de jeu à une autre. Pour en savoir plus sur la persistance dans Verse, consultez la rubrique Utiliser des données persistantes dans Verse.
L'exemple Verse suivant illustre la définition d'un profil de joueur personnalisé dans une classe que vous pouvez stocker, mettre à jour et laisser à la disposition ultérieure d'un joueur. La classe player_profile_data stocke les informations d'un joueur, telles que son EXP acquise, son rang et les quêtes qu'il a accomplies.
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{}