In Verse, è possibile creare una classe che estende la definizione di un'altra classe aggiungendo o modificando i campi e i metodi della stessa. Questo concetto viene spesso chiamato sottoclasse o ereditarietà perché una classe eredita le definizioni dall'altra classe.
Diamo un'occhiata al dispositivo Strumento di progettazione delle classi come esempio di sottoclasse. Con il dispositivo Strumento di progettazione delle classi, è possibile creare classi di personaggi per i personaggi del giocatore che consentono di definire gli attributi e gli inventari specifici di una classe di personaggio, come una classe tank o DPS (danni al secondo).
![]() |
![]() |
| Classe di personaggio DPS creata con il dispositivo Progettista di classi | Classe di personaggio tank creata con il dispositivo Progettista di classi |
In Verse, hai la possibilità di creare una classe tank e una classe dps come questa:
tank := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
DamageReduction : int
dps := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
MovementMultiplier : float
Poiché alcuni dei campi delle due classi sono uguali, è possibile ridurre la duplicazione con una superclasse che contiene le proprietà e i comportamenti condivisi delle classi. Chiamiamo questa superclasse player_character e facciamo tank e dps sottoclassi di player_character:
player_character := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
dps := class(player_character):
MovementMultiplier : float
tank := class(player_character):
DamageReduction : int
Poiché le classi tank e dps sono sottoclassi di player_character, ereditano automaticamente i campi e i metodi della classe player_character, quindi è sufficiente specificare cosa c'è di diverso in questa classe dalla superclasse.
Ad esempio, la classe dps aggiunge soltanto il campo Movement Multiplier e la classe tank aggiunge soltanto il campo DamageReduction. Questa configurazione è utile se si modificano i comportamenti condivisi delle due classi in un secondo momento, perché sarà necessario modificarla solo nella superclasse.
Con Verse, è possibile aggiungere ulteriori modifiche per differenziare le classi tank e dps aggiungendo metodi alle sottoclassi.
Un effetto utile della sottoclasse è che permette di utilizzare la relazione tra una superclasse e le sue sottoclassi. A causa dell'ereditarietà, un'istanza di tank è un player_character specializzato e un'istanza di dps è un player_character specializzato che viene indicato come is-a relationship. Poiché tank e dps sono entrambe sottoclassi della stessa superclasse e divergono dalla loro superclasse condivisa, tank non ha una relazione con dps.
Specificatore override
Per creare istanze di classi con valori iniziali, una pratica comune è avere una funzione che genera le istanze. Ad esempio:
CreateDPSPlayerCharacter() : dps =
return dps{StartingShields := 0, MaxShields := 0, AllowOvershield := false, MovementMultiplier := 1.9}
CreateTankPlayerCharacter() : tank =
return tank{StartingShields := 100, MaxShields := 200, AllowOvershield := true, DamageReduction := 50}
Le funzioni CreateTankPlayerCharacter() e CreateDPSPlayerCharacter() creano le istanze con i valori iniziali appropriati. In alternativa, è possibile eseguire la sostituzione dei campi della superclasse e assegnare valori iniziali per evitare di dover fornire così tanti valori iniziali durante la creazione di un'istanza.
Ad esempio, la classe tank della sezione precedente potrebbe essere simile a questa con le sostituzioni sui campi:
tank := class(player_character):
StartingShields<override> : int = 100
MaxShields<override> : int = 200
AllowOvershield<override> : logic = true
DamageReduction : int = 50
CreateTankPlayerCharacter() : tank =
return tank{}
È anche possibile eseguire l'override dei metodi nella sottoclasse, il che significa che il metodo dell'override può essere impiegato in tutti i contesti in cui sarebbe applicabile il metodo originale. In altre parole, devono valere queste condizioni:
- Il metodo deve accettare almeno ogni argomento accettato dal metodo di cui è stato eseguito l'override, così il tipo di parametro deve essere un supertipo del tipo di parametro della funzione di cui è stato eseguito l'override.
- Il metodo non deve restituire un valore che il metodo di cui è stato eseguito l'override non potrebbe avere, così il tipo restituito deve essere un sottotipo del tipo restituito del metodo di cui è stato eseguito l'override.
- Il metodo non deve avere più effetti del metodo di cui è stato eseguito l'override, così lo specificatore di effetto deve essere un sottotipo dello specificatore di effetto del metodo di cui è stato eseguito l'override.
Super
Come per self, puoi utilizzare (super:) per accedere alle implementazioni di superclassi di campi e metodi. Per utilizzare (super:), il campo o il metodo deve essere implementato nella definizione della superclasse.
pet := class():
Sound : string
Speak() : void =
Log(Sound)
cat := class(pet):
Suono<override> : stringa = "Meow"
Speak<override>() : void =
(super:)Speak() # "Meow" viene visualizzato nel registro Output
Log("Purr") # "Purr" viene visualizzato nel registro Output
Espressioni di blocco nel corpo di una sottoclasse
Tutte le espressioni block che si trovano in una sottoclasse corpo verranno eseguite dopo le espressioni block specificate nel corpo della superclasse. Ad esempio, nel codice seguente, quando viene creata l'istanza della classe cat denominata MrSnuffles, prima viene eseguito Speak() poi Purr().
pet := class():
Speak() : void =
...
block:
Speak()
cat := class(pet):
Purr() : void =
...
block:
Purr()
MrSnuffles := cat{}
Specificatore abstract
Quando una classe o un metodo di classe include lo specificatore abstract, non è possibile creare un'istanza della classe. Le classi abstract sono intese come una superclasse con implementazione parziale o come un'interfaccia comune. Questo è utile per il fatto che non ha senso avere istanze di una superclasse ma non si desidera duplicare proprietà e comportamenti tra classi simili.
Nell'esempio, la classe "pet" è astratta perché non rappresenta un concetto specifico, mentre le sottoclassi "gatto" e "cane" sono istanze concrete che estendono la classe astratta "pet".
pet := class<abstract>():
Speak() : void
cat := class(pet):
Speak() : void =
...
dog := class(pet):
Speak() : void =
...

