En Verse, puedes crear una clase que extienda la definición de otra clase al añadir o modificar los campos y los métodos de la otra clase. Esto suele denominarse subclasificación o herencia, porque una clase hereda definiciones de la otra.
Analicemos el dispositivo de diseñador de clases como un ejemplo de subclase. Con el dispositivo de diseñador de clases, puedes crear clases de personajes para personajes jugadores; esto te permite definir los atributos e inventarios específicos de una clase de personaje, como un personaje tanque o de DPS (daño por segundo).
![]() |
![]() |
| Clase de personaje DPS creada con el dispositivo de diseñador de clases | Clase de personaje tanque creada con el dispositivo de diseñador de clases |
En Verse, puedes crear una clase tank y una clase dps de esta forma:
tank := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
DamageReduction : int
dps := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
MovementMultiplier : float
Como algunos de los campos de las dos clases son iguales, puedes reducir la duplicación con una superclase que contenga las propiedades y los comportamientos compartidos de las clases. Llamaremos a esta superclase player_character y convertiremos a tank y dps en subclases de player_character:
player_character := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
dps := class(player_character):
MovementMultiplier : float
tank := class(player_character):
DamageReduction : int
Como las clases tank y dps son subclases de player_character, heredan automáticamente los campos y métodos de la clase player_character, por lo que solo tienes que especificar lo que es diferente en esta clase con respecto a la superclase.
Por ejemplo, la clase dps solo añade el campo MovementMultiplier, y la clase tank solo añade el campo DamageReduction. Esta configuración resulta útil si cambias el comportamiento compartido de las dos clases más adelante, ya que solo tendrás que modificarlo en la superclase.
Con Verse, puedes añadir más cambios para diferenciar las clases tank y dps añadiendo métodos a las subclases.
Un efecto útil de las subclases es que puedes utilizar la relación entre una superclase y sus subclases. Debido a la herencia, una instancia de tank es un player_character especializado, y una instancia de dps es un player_character especializado; esto se denomina relación is-a. Como tank y dps son subclases de la misma superclase y se distinguen de su superclase compartida, tank no tiene una relación con dps.
Especificador de anulación
Para crear instancias de clases con valores iniciales, se suele utilizar una función que genere las instancias. Por ejemplo:
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}
Las funciones CreateTankPlayerCharacter() y CreateDPSPlayerCharacter() crean las instancias con los valores iniciales adecuados. O bien, puedes anular los campos de la superclase y asignar valores iniciales para no tener que ofrecer tantos valores iniciales al crear una instancia.
Por ejemplo, la clase tanque de la sección anterior podría verse de la siguiente manera con anulaciones en los campos:
tank := class(player_character):
StartingShields<override> : int = 100
MaxShields<override> : int = 200
AllowOvershield<override> : logic = true
DamageReduction : int = 50
CreateTankPlayerCharacter() : tank =
return tank{}
También puedes anular métodos en la subclase, lo que significa que puedes utilizar el método de anulación en cualquier lugar donde se pueda usar el método anulado. Esto significa lo siguiente:
- El método debe aceptar al menos cualquier argumento aceptado por el método anulado, por lo tanto, el tipo de parámetro debe ser un supertipo del tipo de parámetro de la función anulada.
- El método no debe devolver un valor que el método anulado no pueda tener, por lo tanto, el tipo devuelto debe ser un subtipo del tipo devuelto del método anulado.
- El método no debe tener más efectos que el método anulado, por lo tanto, el especificador de efecto debe ser un subtipo del especificador de efecto del método anulado.
Súper
Al igual que con Self, puedes usar (super:) para acceder a las implementaciones de la superclase de los campos y los métodos. Para poder utilizar (super:), el campo o el método debe implementarse en la definición de la superclase.
pet := class():
Sound : string
Speak() : void =
Log(Sound)
cat := class(pet):
Sound<override> : string = "Meow"
Speak<override>() : void =
(super:)Speak() # "Meow" aparece en el registro de salida.
Log("Purr") # "Purr" aparece en el registro de salida.
Expresiones de bloque en el cuerpo de una subclase
Toda expresión block en el cuerpo de una subclase se ejecutará después de las expresiones block especificadas en el cuerpo de la superclase. Por ejemplo, en el siguiente código, cuando se crea la instancia de la clase cat, denominada MrSnuffles, se ejecuta primero Speak() y, luego, Purr().
pet := class():
Speak() : void =
...
block:
Speak()
cat := class(pet):
Purr() : void =
...
block:
Purr()
MrSnuffles := cat{}
Especificador abstracto
Cuando una clase o un método de una clase tiene el especificador abstract, no se puede crear una instancia de la clase. La finalidad de las clases abstractas es usarlas como superclases con implementación parcial o como una interfaz común. Esto es útil para cuando no tiene sentido tener instancias de una superclase, pero no quieres duplicar propiedades y comportamientos en las clases similares.
En el siguiente ejemplo, como pet es un concepto abstracto, una instancia de la clase pet no es lo suficientemente específica, pero un gato (cat) o un perro (dog) como mascota sí tienen sentido, por lo que esas subclases no se marcan como abstractas.
pet := class<abstract>():
Speak() : void
cat := class(pet):
Speak() : void =
...
dog := class(pet):
Speak() : void =
...

