В Verse вы можете создать класс, который расширяет определение другого класса, добавив или изменив его поля и методы. Это часто называют созданием подкласса или наследованием, потому что один класс наследует определения от другого класса.
Разберём устройство «Создание класса» и его использование при создании подклассов. С помощью устройства «Создание класса» можно создавать классы персонажей игроков, которые позволяют определять свойства и инвентарь, характерные для класса персонажа, например для класса «танк» или «урон в секунду» (DPS).
![]() |
![]() |
Класс персонажа dps, созданный с помощью устройства «Создание класса» |
Класс персонажа tank, созданный с помощью устройства «Создание класса» |
В Verse вы можете создать класс tank и класс dps следующим образом:
tank := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
DamageReduction : int
dps := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
MovementMultiplier : float
Поскольку некоторые поля в двух классах одинаковы, можно уменьшить дублирование с помощью суперкласса, который хранит общие свойства и общее поведение классов. Назовём этот суперкласс player_character, а tank и dps сделаем подклассами player_character:
player_character := class():
StartingShields : int
MaxShields : int
AllowOvershield : logic
dps := class(player_character):
MovementMultiplier : float
tank := class(player_character):
DamageReduction : int
Поскольку классы tank и dps являются подклассами player_character, они автоматически наследуют поля и методы класса player_character, поэтому нужно лишь указать, чем эти классы отличаются от суперкласса.
Например, в классе dps добавляется только поле Movement Multiplier, а в классе tank — только поле DamageReduction. Такой подход полезен, если позже потребуется изменить общее поведение двух классов, потому что достаточно будет изменить их только в суперклассе.
С помощью Verse можно внести дополнительные изменения для различения классов tank и dps, добавив методы в подклассы.
Полезным эффектом создания подклассов является то, что можно использовать отношения между суперклассом и его подклассами. Благодаря наследованию экземпляр tankи экземпляр dps являются специализированными подклассами player_character, что называется отношением открытого наследования. Поскольку tank и dps являются подклассами одного суперкласса и наследуют от общего суперкласса, tank не связан отношениями с dps.
Спецификатор override
Для создания экземпляров классов с начальными значениями зачастую используется функция, которая генерирует экземпляры. Например:
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}
Функции CreateTankPlayerCharacter() и CreateDPSPlayerCharacter() создают экземпляры с соответствующими начальными значениями. Другой способ: переопределить поля из суперкласса и присвоить им начальные значения. Так не придётся указывать много начальных значений при создании экземпляра.
Например, класс tank из предыдущего раздела может выглядеть следующим образом после переопределения полей:
tank := class(player_character):
StartingShields<override> : int = 100
MaxShields<override> : int = 200
AllowOvershield<override> : logic = true
DamageReduction : int = 50
CreateTankPlayerCharacter() : tank =
return tank{}
Также можно переопределять методы в подклассе, т. е. вы можете использовать переопределяющий метод везде, где мог быть использован переопределённый метод. То есть:
- Метод должен принимать по крайней мере любой аргумент, принимаемый переопределённым методом, поэтому тип параметра должен быть супертипом типа параметра переопределённой функции.
- Метод не должен возвращать значение, которое не мог бы возвращать переопределённый метод, поэтому возвращаемый тип должен быть подтипом возвращаемого типа переопределённого метода.
- Метод не должен иметь больше эффектов, чем переопределённый метод, поэтому спецификатор эффекта должен быть подтипом спецификатора эффекта переопределённого метода.
Идентификатор super
Подобно слову Self, можно использовать слово (super:) для обращения к реализациям полей и методов суперкласса. Чтобы иметь возможность использовать (super:), поле или метод должны быть реализованы в определении суперкласса.
pet := class():
Sound : string
Speak() : void =
Log(Sound)
cat := class(pet):
Sound<override> : string = "Мяу"
Speak<override>() : void =
(super:)Speak() # "Мяу" появляется в журнале выходных данных
Log("Мур") # "Мур" появляется в журнале выходных данных
Блочные выражения в теле суперкласса
Любые выражения block, находящиеся в теле подкласса, будут выполняться после выражений block, указанных в теле суперкласса. Например, в следующем коде, когда создаётся экземпляр класса cat с именем MrSnuffles, сначала выполняется Speak(), а затем Purr().
pet := class():
Speak() : void =
…
block:
Speak()
cat := class(pet):
Purr() : void =
…
block:
Purr()
MrSnuffles := cat{}
Спецификатор abstract
Когда класс или метод класса имеет спецификатор abstract, создание экземпляра этого класса невозможно. Абстрактные классы используются для частичной реализации суперкласса или в качестве общего интерфейса. Это полезно в тех случаях, когда экземпляры суперкласса не нужны, но необходимо дублировать свойства и поведение аналогичных классов.
В следующем примере, поскольку pet является абстрактной концепцией, экземпляр класса pet не является точным, тогда как pet cat (домашний кот) или pet dog (домашняя собака) являются, поэтому соответствующие подклассы не маркированы как абстрактные.
pet := class<abstract>():
Speak() : void
cat := class(pet):
Speak() : void =
…
dog := class(pet):
Speak() : void =
…

