# Объектно-ориентированные возможности

В язык была добавлена поддержка классов и наследования, по возможности совместимая с JavaScript (opens new window). Синтаксис классов совпадает с JavaScript полностью, семантика отличается незначительно. Ниже вкратце описаны все объектно-ориентированные возможности языка и особенности их реализации.

# Общая информация

Класс определяется с помощью ключевого слова Класс:

Класс ДомашнееЖивотное
{
}

Создать экземпляр класса можно с помощью оператора Новый

перем питомец = Новый ДомашнееЖивотное()

Он похож на объект в том смысле, что хранит в себе набор пар ключ-значение, называемых атрибутами

питомец.имя = "Бобик"

Атрибуты принадлежат экземплярам класса: каждый экземпляр имеет собственный набор атрибутов, который может отличаться от других

перем кошка = Новый ДомашнееЖивотное()
кошка.имя = "Мурка"

перем собака = Новый ДомашнееЖивотное()
собака.имя = "Бобик"
собака.порода = "Овчарка"

Методы же класса принадлежат самому классу, т.е. все экземпляры конкретного класса имеют один и тот же набор методов

Класс ДомашнееЖивотное
{
   Позвать()
   {
      Консоль.Лог(этот.имя + ", ко мне!")
   }
}

перем собака = Новый ДомашнееЖивотное()
собака.имя = "Бобик"
собака.Позвать()
# напечатает "Бобик, ко мне!"

перем кошка = Новый ДомашнееЖивотное()
кошка.имя = "Мурка"
кошка.Позвать()
# напечатает "Мурка, ко мне!"

Внутри методов класса можно обратиться к атрибутам конкретного экземпляра с помощью ключевого слова этот. Атрибуты класса удобно инициализировать в специальном методе - конструкторе. Он имеет имя Конструктор, и неявно вызывается оператором Новый в процессе создания экземпляра. Все аргументы, переданные в оператор Новый, становятся аргументами конструктора

Класс ДомашнееЖивотное
{
   Конструктор(имя)
   {
      этот.имя = имя
   }
}

перем собака = Новый ДомашнееЖивотное("Бобик")

Консоль.Лог(собака.имя)
# напечатает "Бобик"

Помимо атрибутов класс может иметь свойства (вычисляемые атрибуты). Обращение к свойству синтаксически выглядит как обращение к атрибуту, но при этом на самом деле вызывается соответствующий метод класса

Класс ДомашнееЖивотное
{
   Конструктор(имя)
   {
      этот.имя = имя
   }

   Получить имя()
   {
      Вернуть этот._имя
   }

   Установить имя(значение)
   {
      Если (значение == "")
         ВызыватьИсключение("Имя не может быть пустой строкой")

      этот._имя = значение
   }
}

перем собака = Новый ДомашнееЖивотное("Бобик")

Консоль.Лог(собака.имя)
# напечатает "Бобик", вызывается метод Получить имя()

собака.имя = ""
# вызывается метод Установить имя(значение), который
# сгенерирует исключение

Свойства определяются с помощью ключевых слов Получить (геттер свойства) и Установить (сеттер свойства) перед именем метода. Само имя метода становится именем свойства. Свойство может не иметь сеттера (в этом оно будет предназначено только для чтения) либо геттера (довольно редкий случай - только для записи).

Классы могут наследоваться друг от друга

Класс ДомашнееЖивотное
{
   Конструктор(имя)
   {
      этот.имя = имя
   }

   Позвать()
   {
      # ...
   }
}

Класс Кошка расширяет ДомашнееЖивотное
{
   Конструктор(имя)
   {
      Базовый(имя)
   }
}

Класс Собака расширяет ДомашнееЖивотное
{
   Конструктор(имя)
   {
      Базовый(имя)
   }

   ПринестиТапки()
   {
      # ...
   }
}

Дочерние классы получают все методы родительского класса кроме конструктора (Кошка и Собака будут иметь метод Позвать), а также могут определять свои собственные методы и свойства (Собака будет иметь метод ПринестиТапки). Конструктор дочернего класса должен обязательно вызывать конструктор родительского с помощью ключевого слова Базовый.

Дочерние классы могут переопределять методы (и свойства, поскольку они также являются методами) родительского класса, т.е. иметь собственные методы с таким же именем и параметрами, как в родительском классе, но другой реализацией. Внутри переопределенного метода можно вызвать метод родительского класса с помощью ключевого слова Базовый

Класс ДомашнееЖивотное
{
   Конструктор(имя)
   {
      этот.имя = имя
   }

   ПодатьГолос()
   {
      Вернуть этот.имя + " говорит "
   }
}

Класс Кошка расширяет ДомашнееЖивотное
{
   Конструктор(имя)
   {
      Базовый(имя)
   }

   ПодатьГолос()
   {
      Вернуть базовый.ПодатьГолос() + " мяу"
   }
}

Класс Собака расширяет ДомашнееЖивотное
{
   Конструктор(имя)
   {
      Базовый(имя)
   }

   ПодатьГолос()
   {
      Вернуть базовый.ПодатьГолос() + " гав"
   }
}

перем собака = Новый Собака("Бобик")
собака.ПодатьГолос()          # вернет "Бобик говорит гав"

перем кошка = новый Кошка("Мурка")
кошка.ПодатьГолос()           # вернет "Мурка говорит мяу"

Проверить, является ли объект экземпляром указанного класса (либо экземпляром его наследника), можно с помощью оператора экземпляр:

кошка экземпляр Кошка               # true
кошка экземпляр ДомашнееЖивотное    # true
кошка экземпляр Собака              # false

# Особенности реализации классов

Классы в языке "Стек" не имеют статических методов и атрибутов. Кроме того, нельзя менять сам прототип класса (добавлять или удалять методы) и присваивать классу или экземпляру другой прототип.

Классы поддерживают заплатки, а именно, если в нескольких файлах имеется несколько определений одного и того же класса, они все будут объединены в один класс. Приоритет при объединении имеют члены того класса, который был встречен позднее. Например, классы

# файле Main.prg

Класс К
{
   Конструктор(парам)
   {
      этот.парам = парам
   }

   Метод()
   {
      Вернуть этот.парам + 1
   }
}

# файле Patch.prg

Класс К
{
   Метод()
   {
      Вернуть этот.парам + 2
   }

   ДопМетод()
   {
      Вернуть этот.парам * этот.парам
   }
}

будут объединены следующим образом:

Класс К
{
   # из Main
   Конструктор(парам)
   {
      этот.парам = парам
   }

   # заменен в Patch
   Метод()
   {
      Вернуть этот.парам + 2
   }

   # добавлен в Patch
   ДопМетод()
   {
      Вернуть этот.парам * этот.парам
   }
}

# Интроспекция

Интроспекция позволяет получить информацию о классе (список методов и цепочку наследования), имея ссылку на класс. Это очень полезная возможность, но в повседневном коде она используется редко, поэтому детали здесь рассматриваться не будут.