Введение в Классы и Объекты
Оригинал Introduction to Classes and Objects
Перевод выполнен участником форума ProgZ c ником Krys
Редакция Victor Yukhtenko
Целью этого руководства является введение в основы объектно-ориентированного программирования в VIP, и демонстрация примеров синтаксиса.
Объектная модель
Основными элементами объектной модели VIP являются объекты, объектные типы и классы.
Основными средствами при работе с ними являются интерфейсы (interfaces), объявления классов(class declarations) и исполнения (implementations).
Интерфейсом называется именованный набор объявлений предикатов и оформленный в виде раздела interface. Интерфейсы описывают доступ к объектам извне объекта. Интерфейсы представляют собой объектные типы.
Рассмотрим следующее определение интерфейса:
interface person predicates getName : () -> string Name. setName : (string Name). end interface person
Это определение интерфейса с именем person. В этом примере объекты типа person имеют два предиката getName и setName, которые объявляются в этом разделе.
Интерфейсы только определяют типы объектов, классы порождают объекты. Класс имеет раздел class объявления (declaration) и раздел implement исполнения. Класс, который порождает объект person, можно объявить так:
class person : person constructors new : (string Name). end class person
Это объявление класса с именем person, который создает объект типа person. Класс имеет конструктор (contructor) с именем new, который при вызове и порождает объект типа person, передавая при этом новому объекту строку с именем Name. У класса должен быть раздел исполнения (implement), который может выглядеть так:
implement person facts name : string. clauses new(Name) :- name := Name. clauses getName() = name. clauses setName(Name) :- name := Name. end implement person
Как видно, в этом разделе должны присутствовать определения для каждого из видимых извне (public) предикатов и конструкторов, т.е. для new, getName и setName. В разделе implement можно, кроме того, объявить и определить дополнительные локальные элементы, доступ к которым возможен только из самого раздела implement. Так, класс содержит объявление факта-переменной name, который будет использоваться для хранения имени персоны.
Каждый из объектов при создании получает свою собственную реализацию факта-переменной. Выше приведенные клозы будут ссылаться только на конкретную реализацию факта-переменной внутри объекта. Мы говорим, что этот предикат является объектным предикатом (object predicate), а факт является объектным фактом (object fact).
Элементы класса
Класс может иметь элементы, общие для всех объектов этого класса. Расширим пример, добавив в него код, который позволяет подсчитывать количество созданных объектов класса person. Счетчик будет увеличиваться каждый раз, когда создается новый объект, и никогда не будет уменьшаться. Расширим объявление класса, добавив предикат getCreatedCount (получить значение счетчика созданий), который сможет возвращать текущее значение счетчика:
class person : person constructors new : (string Name). predicates getCreatedCount : () -> unsigned Count. end class person
Отметим, что видимый извне предикат класса, объявлен в объявлении класса, тогда как видимый извне объектный предикат объявлен в интерфейсе. Из этого правила нет исключений; невозможно объявить объектный предикат в объявлении класса, и невозможно объявить предикат класса в разделе интерфейса.
После объявления, каждый предикат должен быть определен в разделе implement (имплементация) класса. То же самое нужно сделать для факта, который будет хранить счетчик. Этот факт должен быть фактом класса, т.е. быть общим для всех объектов. В разделе implement класса можно объявить локальные (private) как элементы объекта, так и элементы класса. Для объявления элементов класса нужно вставить ключевое слово class перед соответствующим объявлением. Например, имплементация класса person может быть представлена таким образом:
implement person class facts createdCount : unsigned := 0. clauses getCreatedCount() = createdCount. facts name : string. clauses new(Name) :- name := Name, createdCount := createdCount+1. clauses getName() = name. clauses setName(Name) :- name := Name. end implement person
Здесь мы добавили факт класса createdCount и инициализировали его нулевым значением. Мы также добавил клоз для предиката getCreatedCount, который возвращает текущее значение переменной createdCount. И, наконец, мы добавили в текст клоза-конструктора строку, которая увеличивает createdCount на единицу.
Отметим, что в конструкторе два присваивания имеют похожий вид, но одно присваивание createdCount := createdCount+1, оперирующее с фактом класса, меняет состояние класса, а другое - name := Name, оперирующее с фактом объекта, меняет состояние объекта.
Модули
Это особые варианты классов, которые вообще не порождают объектов, поэтому они действуют, как модули, а не как классы. Класс, не конструирующий объекты (т.е., модуль), объявляется без указания объектного типа:
class io % тип не указан! predicates write : (string ToWrite). write : (unsigned ToWrite). end class io
Такой класс не может порождать объекты.
Создание и доступ
С использованием кода, приведенного выше, можно задать цель, в которой создается объект, который используется классом io (применение которого мы здесь обсуждать не будем) для выдачи на печать имени персоны.
goal P = person::new("John"), Name = P:getName(), io::write(Name).
В первой строке происходит вызов конструктора (constructor) new класса person. Созданный объект запоминается в переменной P.
Во второй строке, переменной Name присваивается результат исполнения объектного предиката getName объекта P.
В последней строке происходит вызов предикат write класса io с передачей ему имени Name.
Отметим, что на имена классов ссылаются с помощью символа ‘::’, например, person::new(...). А объектные предикаты ссылаются на объекты с использованием символа ‘:’, напрмер, P:getName. Наконец, отметим, что конструкторы являются функциями, которые возвращают объекты, даже если эти конструкторы не объявлены, как функции. Тип возвращаемого значения определяется исходя из объявления класса, к которому применяется конструктор.
Интерфейсы как объектные типы
Как уже говорилось, интерфейсы являются объектными типами. Это следует понимать буквально, вы можете использовать интерфейсы везде, где используются обычные необъектные типы. Например, в объявлении предиката:
class mail predicates sendMessage : (person Recipient, string Message). end class mail
Предикат mail::sendMessage имеет типы аргументов: person (т.е., имя интерфейса) и string.
Разнообразие имплементаций
Можно создать несколько различных классов, которые могут создавать одинаковые объекты person. Вы просто объявляете классы с разделами implement, которые содержат свои конструкторы объектов person. Разделы implement могут быть различными. Например, можно создать класс, который сохраняет персоны в базе данных, а не в факт-переменной. Вот, объявление такого класса:
class personInDB : person constructors new : (string DatabaseName, string Name). end class personInDB
В этом руководстве не рассматриваются конкретные реализации, но следующий код демонстрирует, что объекты одинаковых типов могут иметь различные разделы implement.
implement personInDB facts db : myDatabase. personID : unsigned. clauses new(DatabaseName, Name) :- db := myDatabase::getDB(DatabaseName), personID := db:storePerson(Name). clauses getName() = db:getPersonName(personID). clauses setName(Name) :- db:setPersonName(personID, Name). end implement personInDB
Обратите внимание, что различается не только внутреннее поведение объекта, но и внутреннее состояние также имеет разную структуру и содержание.
Категорированный полиморфизм
Объекты одного типа могут использоваться в одинаковых контекстах, независимо от различия в разделах implement. Можно, например, послать сообщение некоей персоне, используя почтовый класс, объявленный заранее, не обращая внимание, на то, каким конструктором объект person был создан: person или personInDB:
goal P1 = person::new("John"), mail::sendMessage(P1, "Hi John, ..."), P2 = personInDB::new("Paul"), mail::sendMessage(P2, "Hi Paul, ...").
Это поведение известно, как категорирование: в некоторых контекстах, объекты, созданные с помощью одного классом, используются наравне с объектами, созданными с помощью другого класса, если оба объекта имеют один и тот же тип, который требуется контекстом.
Отметим, что предикат mail::sendMessage принимает объекты любого класса person, поэтому этот предикат является, в определенном смысле, полиморфным.
Supports (поддерживает) - как расширение типов
Пусть наша программа имеет дело со специфическими персонами, а именно, - с пользователями программ. Пользователи являются персонами с именами и, кроме того, они имеют пароли. Создадим новый интерфейс (тип) объекта для пользователей, который бы устанавливал, что пользователь является персоной с паролем. Для этого используем квалификацию supports:
interface user supports person predicates trySetPassword : (string Old, string New, string Confirm) determ. validatePassword : (string Password) determ. end interface user
Интерфейс user поддерживает интерфейс person. Это означает, что:
- Объекты типа user будут исполнять также предикаты, объявленные в интерфейсе person, а именно, getName и setName.
- Объекты типа user являются одновременно объектами типа person и, поэтому, могут использоваться в тех же контекстах, что и объекты person.
Допустим, что имеется некоторый класс user.
class user : user constructors new : (string Name, string Password). end class user
Тогда объекты этого класса могут быть использованы предикатом mail::sendMessage:
goal P = user::new("Benny", "MyCatBobby"), mail::sendMessage(P, "Hi Benny, ...").
Поддержка интерфесом других интерфейсов означает, что:
- Объекты данного типа должны поддерживать (предоставлять право вызова и исполнять) предикаты всех интерфейсов, которые поддерживаются.
- Объекты данного типа являются также объектами всех типов, соответствующих поддерживаемым интерфейсам.
Квалификация supports порождает, таким образом, иерархию подтипов. Мы говорим, что user является подтипом person.
object - первичный тип
Интерфейс object неявно поддерживает все интерфейсы. Интерфейс object является интерфейсом, не имеющим содержания, т.е. в нем нет объявленных предикатов. Объектный интерфейс object поддерживает, прямо или непрямо, любой другой интерфейс, поэтому все объекты имеют тип объектного интерфейса object. Объектный интерфейс object является супертипом для любого объекта.
Наследование
Для применения класса user, можно использовать свойства одного из наших классов person. Класс user похож на класс person, за исключением того, что user имеет дело еще и с паролями. Хотелось бы, чтобы наш класс user наследовал раздел implement из класса person. Это можно сделать с помощью квалификации inherits, как показано ниже:
implement user inherits person facts password : string. clauses new(Name, Password) :- person::new(Name), password := Password. clauses trySetPassword(Old, New, Confirm) :- validatePassword(Old), New = Confirm, password := New. clauses validatePassword(Password) :- password = Password. end implement user
Раздел implement подтверждает, что этот класс наследует (inherits) свойства класса person. Отсюда следует, что:
- Объект person встраивается в каждый созданный объект user.
- Все предикаты, объявленные в интерфейсе person, могут быть наследованы из класса person в класс user.
Когда наследуется какой-нибудь предикат, определять его в разделе implement не нужно, т.к. наследуется определение предиката из раздела implement наследуемого класса.
Можно получить тот же результат с помощью следующего кода (клозы для предикатов, обрабатывающие пароли остаются теми же):
implement user facts person : person. password : string. clauses new(Name, Password) :- person := person::new(Name), password := Password. clauses getName() = person:getName(). clauses setName(Name) :- person:setName(Name). end implement user
Действие этого кода похоже на действие предыдущего фрагмента, но размер текста увеличился. В этом фрагменте наследования из класса person не происходит. Вместо этого создан объект person и он сохранен в одноименном факте-переменной. Вместо наследования кода getName и setName они просто переопределены и теперь обращаются за решением к объекту из факт-переменной. Такие обращения за решением можно сократить с помощью ключевого слова delegate. Код в этом случае сократится.
implement user delegate interface person to person facts person : person:=erroneous. password : string. clauses new(Name, Password) :- person := person::new(Name), password := Password. end implement user_class
Функционирование то же, но:
- Вызовы предикатов, относящихся к объекту person транзитом передаются в него
- Mожно динамически изменить значение в факт-переменной на другой объект, например, на объект класса personInDB простым присваиванием нового объекта факту-переменной.
VIP обрабатывает такие непрямые обращения довольно эффективно, но в случае наследования обработка все же происходит гораздо быстрее.
VIP дает возможность многократного наследования, т.е., вы можете одновременно наследовать многие классы.
Заключение
Вы познакомились с самыми основными концепциями объектной системы в VIP. В ней имеются и другие интересные особенности, о которых здесь не было упомянуто, например,:
- Объекты могут поддерживать добавочные интерфейсы в разделе implement.
- Предикаты завершения, освобождающие занятую память.
- и т.д.