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:
…
# Допустимое выражение
HeadTilt:rotation = IdentityRotation()
Создание класса
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 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.
Напротив, поле Name
в cat
не является изменяемой переменной и поэтому по умолчанию не может быть изменено. Это означает, что можно задать его значение по умолчанию во время создания, но после создания его нельзя будет изменить — это неизменяемое поле.
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â€}
Обращение к полям
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()
.
Обе кошки имеют поля с одинаковыми именами, но эти поля содержат разные значения. Например, OldCat.Meow()
и Kitten.Meow()
возвращают разный результат, потому что их поля Sound
содержат разные значения.
Self
Self
— это специальный идентификатор в языке Verse, который можно использовать в методе класса для ссылки на экземпляр класса, из которого метод был вызван. Вы можете ссылаться на другие поля экземпляра, из которого был вызван метод, не используя Self
, но если нужно сослаться на экземпляр в целом, то следует использовать Self
.
Например, если в DisplayMessage
требуется аргумент, задающий питомца, с которым нужно связать сообщение:
DisplayMessage(Pet:pet, Message:string) : void = …
cat := class:
…
Meow() : void = DisplayMessage(Self, Sound)
Если вам потребуется инициализировать более громкую версию мяуканья кошек, вы, скорее всего, заходите использовать уже настроенную переменную Sound
. Однако в следующем коде это не сработает, поскольку LoudSound
не может ссылаться на составляющую элемент экземпляра Sound
из-за того, что выражения со значениями по умолчанию не используют идентификатор Self
.
cat := class:
…
Sound : string
Meow() : void = DisplayMessage(Self, Sound)
# Следующее не даст результата, поскольку значения класса по умолчанию
# не могут ссылаться на Self
LoudSound : string = "Loud " + Self.Sound
LoudMeow() : void = DisplayMessage(Self, LoudSound)
Подклассы и наследование
Классы могут наследоваться от суперкласса, который включает все поля суперкласса в наследующий класс. Такие классы называются подклассами суперкласса. Например:
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)
Здесь использование class(pet)
при определении cat
и dog
объявляет, что они наследуются от класса pet
. Иначе говоря, они являются подклассами pet
.
Это даёт несколько преимуществ:
- 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
Дополнительную информацию можно найти на странице «Подкласс».
Переопределение
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
Вызов методов
При обращении к полю экземпляра класса вы получаете доступ к значению поля этого экземпляра. Для методов поле является функцией и его переопределение заменяет значение поля новой функцией. При вызове метода вызывается значение поля. Это означает, что вызываемый метод определяется экземпляром. Рассмотрим следующий пример:
pet := class:
…
OnHearName() : void = {}
cat := class(pet):
…
OnHearName<override>() : void = Meow()
dog := class(pet):
…
OnHearName<override>() : void = DoTrick()
CallFor(Pet:pet):void=
DisplayMessage("Эй, {Pet.Name}!")
Pet.OnHearName()
Если написать CallFor(Percy)
, то будет вызван метод OnHearName
, определённый в cat
. Если же написать CallFor(Fido)
, где Fido
является экземпляром класса dog
, то будет вызван метод OnHearName
, определённый в dog
.
Спецификаторы видимости
Можно добавить спецификаторы видимости к полям и методам класса, чтобы определить, кто имеет к ним доступ. Например, можно добавить спецификатор private
к полю Sound
, чтобы доступ к этому частному полю имел только класс-владелец.
cat := class:
…
Sound<private> : string
MrSnuffles := cat{Sound := "Мурр"}
MrSnuffles.Sound # Ошибка: доступ к полю private невозможен
Ниже перечислены все спецификаторы видимости, которые можно использовать с классами:
- 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.
Спецификаторы доступа
Вы можете добавлять спецификаторы доступа к классу, чтобы определять, где можно создавать экземпляры. Это пригодится, к примеру, когда необходимо, чтобы экземпляр класса создавался только в определённой области видимости.
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
Вызов конструктора для класса cat
за пределами модуля pets
приведёт к ошибке, поскольку ключевое слово class
имеет спецификатор доступа internal
. Ошибка возникнет несмотря на то, что идентификатор класса имеет спецификатор доступа public
, т. е. к классу cat
можно обращаться в коде за пределами модуля pets
.
Ниже перечислены все спецификаторы доступа, которые можно использовать с ключевым словом class:
- public: Unrestricted access. This is the default access.
- internal: Access limited to the current module.
Спецификатор concrete
Когда класс имеет спецификатор concrete
, его можно создать с пустым архетипом, таким как cat{}
. Это означает, что каждое поле класса должно иметь значение по умолчанию. Кроме того, каждый подкласс класса concrete сам должен иметь спецификатор concrete.
Например:
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.
Спецификатор unique
Спецификатор unique
можно применить к классу, чтобы сделать его уникальным. Чтобы создать экземпляр уникального класса, язык Verse выделяет уникальный идентификатор для полученного экземпляра. Это позволяет определить, одинаковы экземпляры уникальных классов или нет, сравнивая их идентификаторы. Классы без спецификатора unique
не имеют таких идентификаторов, поэтому их можно оценивать на эквивалентность только на основе значений их полей.
Это означает, что уникальные классы можно сравнивать с помощью операторов =
и <>
и что они являются подтипами типа comparable
.
Пример:
unique_class := class<unique>:
Field : int
Main()<decides> : void =
X := unique_class{Field := 1}
X = X # X тождествен себе
Y := unique_class{Field := 1}
X <> Y # X и Y уникальны и поэтому не равны
Спецификатор final
Cпецификатор final
можно использовать только для классов и полей классов.
Когда у класса есть спецификатор final
, невозможно создать его подкласс. В следующем примере невозможно использовать класс pet
в качестве суперкласса, потому что он имеет спецификатор 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
…
Блочные выражения в теле класса
Вы можете использовать блочные выражения в теле класса. При создании экземпляра класса выражения block
выполняются в том порядке, в котором они определены. Функции, вызываемые в выражениях block
в теле класса, не могут иметь эффекта 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 := "Гарфилд", Age := 20, Sound := "Рррр"}
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.
Интерфейсы
Интерфейсы — это ограниченная форма классов, которые могут содержать только методы, не имеющие значения. Классы могут наследоваться только от одного (другого) класса, но от любого количества интерфейсов.