Введение в Классы и Объекты
Перевод выполнен участником форума ProgZ c ником Krys
Written by Thomas Linder Puls
Prolog Development Center A/S
Last updated:
Целью этого руководства является введение в основы объектно-ориентированного программирования в VIP, и демонстрация примеров синтаксиса.
Объектная модель
Основными элементами объектной модели VIP являются объекты, объектные типы и классы.
Основными средствами при работе с ними являются интерфейсы (interfaces), объявления классов(class declarations) и применения (implementations).
Интерфейсом называется именованный набор объявлений предикатов. Интерфейсы описывают доступ к объектам извне объекта. Интерфейсы представляют собой объектные типы.
Рассмотрим следующее определение интерфейса:
interface person predicates getName : () -> string Name. setName : (string Name). end interface person
Это определение интерфейса с именем person. В этом примере объекты типа person имеют два предиката getName и setName, которые объявляются в этом разделе.
Интерфейсы только определяют типы объектов, классы порождают объекты. Класс имеет разделы declaration и implementation. Класс, который порождает объект person, можно объявить так:
class person : person constructors new : (string Name). end class person
Это объявление класса с именем person, который создает объект типа person. Класс имеет конструктор с именем new, который при вызове и порождает объект типа person, передавая при этом новому объекту строку с именем Name. У класса должен быть раздел применения (implementation), который может выглядеть так:
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. В разделе implementation можно, кроме того, объявить и определить дополнительные локальные элементы, доступ к которым возможен только из самого раздела implementation. Так, класс содержит объявление факта-переменной name, который будет использоваться для хранения имени персоны.
Каждый из объектов при создании получает свою собственную реализацию факта-переменной. Выше приведенные клозы будут ссылаться только на конкретную реализацию факта-переменной внутри объекта. Мы говорим, что этот предикат является объектным предикатом (object predicate), а факт является объектным фактом (object fact).
Элементы класса
Класс может иметь элементы, общие для всех объектов этого класса. Расширим пример, добавив в него код, который позволяет подсчитывать количество созданных объектов класса person. Счетчик будет увеличиваться каждый раз, когда создается новый объект, и никогда не будет уменьшаться. Расширим объявление класса, добавив предикат getCreatedCount (получить значение счетчика созданий), который сможет возвращать текущее значение счетчика:
class person : person constructors new : (string Name). predicates getCreatedCount : () -> unsigned Count. end class person
Отметим, что видимый извне предикат класса, объявлен в объявлении класса, тогда как видимый извне объектный предикат объявлен в интерфейсе. Из этого правила нет исключений; невозможно объявить объектный предикат в объявлении класса, и невозможно объявить предикат класса в разделе интерфейса.
После объявления, каждый предикат должен быть определен в разделе implementation класса. То же самое нужно сделать для факта, который будет хранить счетчик. Этот факт должен быть фактом класса, т.е. быть общим для всех объектов. В разделе implementation класса можно объявить локальные (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 % no type here 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, ...").
An interface can support several other interfaces, meaning that:
- the objects of that type must provide all the predicates in the supported interfaces
- the objects of that type have all the other types as well
Интерфейс может поддерживать несколько других интерфейсов, это означает, что:
- Объекты должны предоставлять все предикаты тем объектам, чьи интерфейсы поддерживаются.
- Объекты также имеют все типы поддерживаемых интерфейсов
Интерфейс может поддерживать один или более интерфейсов, которые, в свою очередь, могут поддерживать один или более интерфейсов и т.д. В этом случае:
- Объект должен предоставлять все предикаты для поддерживаемых, прямо или непрямо, интерфейсов.
- Объект имеет, одновременно, все типы объектов поддерживаемых прямо или непрямо интерфейсов.
Поддерживающая квалификация порождает иерархию подтипов. Мы говорим, что user является подтипом person.
Объект: первичный тип
Интерфейс, который неявно поддерживает все интерфейсы, является объектным интерфейсом. Объектный интерфейс является интерфейсом, не имеющим содержания, т.е. в нем нет объявленных предикатов. Объектный интерфейс поддерживает, прямо или непрямо, любой другой интерфейс, поэтому все объекты имеют тип объектного интерфейса. Объектный интерфейс является супертипом для любого объекта.
Наследование
Для применения класса user, можно использовать свойства одного из наших классов person. Класс user похож на класс person, за исключением того, что user имеет дело еще и с паролями. Хотелось бы, чтобы наш класс user наследовал раздел implement из класса person. Это можно сделать с помощью следующего кода:
Раздел implement подтверждает, что этот класс наследует (inherits) свойства класса person. Отсюда следует, что:
- Объект person встраивается в каждый сконструированный объект user.
- Все предикаты, объявленные в интерфейсе person, могут быть наследованы из класса person в класс user.
Когда наследуется какой-нибудь предикат, определять его в разделе implement не нужно, т.к. наследуется определение предиката из раздела implement наследуемого класса.
Можно получить тот же результат с помощью следующего кода (клозы для предикатов, обрабатывающие пароли остаются теми же):
implement user facts person : person. password : string. clauses new(Name, Password) :- person := person_class::new(Name), password := Password. clauses getName() = person:getName(). clauses setName(Name) :- person:setName(Name). ... end implement user_class
В этом фрагменте наследования из класса person не происходит. Вместо этого создан объект person и он сохранен в факт-переменной. Вместо наследования кода getName и setName они просто переопределены и теперь обращаются за решением к объекту из факт-переменной. Такие обращения за решением можно сократить с помощью ключевого слова delegate. Действие этого кода похоже на действие предыдущего фрагмента, но есть заметные различия:
- Прежде всего, увеличился размер текста.
- Объект person не встраивается в объект user, а, вместо этого, оюъект ссылается на него. Кроме того, увеличился размер занятой памяти.
- В последнем случае, можно динамически изменить значение в факт-переменной на другой объект, например, на объект класса personInDB простым присваиванием нового объекта факт-переменной.
Следует отметить, что второй вариант раздела implement имеет дополнительный непрямой вызов. VIP обрабатывает такие непрямые обращения довольно эффективно, но в случае наследования обработка все происходит гораздо быстрее. VIP дает возможность многократного наследования, т.е., вы можете одновременно наследовать многие классы.
Заключение
Вы познакомились с самыми основными концепциями объектной системы в VIP. В ней имеются и другие интересные особенности, о которых здесь не было упомянуто, например,: • Объекты могут поддерживать добавочные интерфейсы в разделе implement. • Предикаты завершения, освобождающие занятую память. • и т.д.