Введение в Классы и Объекты

Материал из wikiru.visual-prolog.com

Перевод выполнен участником форума 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).

В первой строке происходит вызов конструктора new из класса person. Созданный объект запоминается в переменной P. Во второй строке, переменной Name присваивается результат исполнения объектного предиката getName объекта P. В последней строке происходит вызов предикат write класса io с передачей ему имени Name. Отметим, что на имена классов ссылаются с помощью символа ‘::’, например, person::new. А объектные предикаты ссылаются на объекты с использованием символа ‘:’, напрмер, P:getName. Наконец, отметим, что конструкторы являются функциями, которые возвращают объекты, даже если эти конструкторы не объявлены, как функции. Тип возвращаемого значения определяется исходя из объявления класса.

Интерфейсы как объектные типы

Как уже говорилось, интерфейсы являются объектными типами. Это следует понимать буквально, вы можете использовать интерфейсы везде, где используются обычные необъектные типы. Например, в объявлении предиката: Предикат mail::sendMessage имеет типы аргументов: person (т.е., имя интерфейса) и string.

Многократное применение

Можно создать несколько различных классов, которые могут создавать одинаковые объекты person. Вы просто объявляете классы с разделами implementation, которые содержат свои конструкторы объектов person. Разделы implementation могут быть различными. Например, можно создать класс, который сохраняет персоны в базе данных, а не в факт-переменной. Вот, объявление такого класса:

В этом руководстве не рассматриваются конкретные реализации, но следующий код демонстрирует, что объекты одинаковых типов могут иметь различные разделы 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 or personInDB: <vpi> goal

 P1 = person::new("John"),
 mail::sendMessage(P1, "Hi John, ..."),
 P2 = personInDB::new("Paul"),
 mail::sendMessage(P2, "Hi Paul, ...").

</vip> Это поведение известно, как категорирование: в некоторых контекстах, объекты, сконструированные одним классом, используются наравне с объектами, сконструированными другим классом, если оба объекта имеют один и тот же тип, который требует контекст. Отметим, что предикат mail::sendMessage принимает объекты любого класса person, поэтому этот предикат является, в определенном смысле, полиморфным.

Поддержка (Расширение типов)

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

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. Тогда объекты этого класса могут быть использованы предикатом mail::sendMessage:

goal
  P = user_class::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. Это можно сделать с помощью следующего кода:

[/indent]Раздел 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. • Предикаты завершения, освобождающие занятую память. • и т.д.