Введение в Классы и Объекты: различия между версиями

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

 
(не показано 26 промежуточных версий этого же участника)
Строка 1: Строка 1:
'''Перевод выполнен участником форума ProgZ c ником Krys'''
Оригинал [http://wiki.visual-prolog.com?title=Introduction_to_Classes_and_Objects Introduction to Classes and Objects]


Written by Thomas Linder Puls
'''Перевод выполнен участником форума [http://www.progz.ru/forum/index.php?showforum=10 ProgZ] c ником Krys'''
 
Prolog Development Center A/S
 
Last updated:


'''Редакция Victor Yukhtenko'''


Целью этого руководства является введение в основы объектно-ориентированного программирования в VIP, и демонстрация примеров синтаксиса.
Целью этого руководства является введение в основы объектно-ориентированного программирования в VIP, и демонстрация примеров синтаксиса.
Строка 13: Строка 10:
Основными элементами объектной модели VIP являются объекты, объектные типы и классы.
Основными элементами объектной модели VIP являются объекты, объектные типы и классы.


Основными средствами при работе с ними являются интерфейсы ('''interfaces'''), объявления классов('''class''' declarations) и применения ('''implementations''').
Основными средствами при работе с ними являются интерфейсы (interfaces), объявления классов(class declarations) и исполнения (implementations).


Интерфейсом называется именованный набор объявлений предикатов. Интерфейсы описывают доступ к объектам извне объекта. Интерфейсы представляют собой объектные типы.
'''Интерфейсом''' называется именованный набор объявлений предикатов и оформленный в виде раздела '''interface'''. Интерфейсы описывают доступ к объектам извне объекта. Интерфейсы представляют собой объектные типы.


Рассмотрим следующее определение интерфейса:
Рассмотрим следующее определение интерфейса:
Строка 27: Строка 24:
Это определение интерфейса с именем '''''person'''''. В этом примере объекты типа '''''person''''' имеют два предиката ''getName'' и ''setName'', которые объявляются в этом разделе.
Это определение интерфейса с именем '''''person'''''. В этом примере объекты типа '''''person''''' имеют два предиката ''getName'' и ''setName'', которые объявляются в этом разделе.


Интерфейсы только определяют типы объектов, классы порождают объекты. Класс имеет разделы '''declaration''' и '''implementation'''. Класс, который порождает объект '''''person''''', можно объявить так:
Интерфейсы только определяют типы объектов, классы порождают объекты. Класс имеет раздел '''class''' объявления (declaration) и раздел '''implement''' исполнения. Класс, который порождает объект '''''person''''', можно объявить так:
<vip>
<vip>
class person : person
class person : person
Строка 34: Строка 31:
end class person
end class person
</vip>
</vip>
Это объявление класса с именем '''''person''''', который создает объект типа '''''person'''''. Класс имеет конструктор с именем ''new'', который при вызове и порождает объект  типа '''''person''''', передавая при этом новому объекту строку с именем ''Name''.
Это объявление класса с именем '''''person''''', который создает объект типа '''''person'''''. Класс имеет конструктор (contructor) с именем ''new'', который при вызове и порождает объект  типа '''''person''''', передавая при этом новому объекту строку с именем ''Name''.
У класса должен быть раздел применения ('''implementation'''), который может выглядеть так:
У класса должен быть раздел исполнения ('''implement'''), который может выглядеть так:
<vip>
<vip>
implement person
implement person
Строка 50: Строка 47:
end implement person
end implement person
</vip>
</vip>
Как видно, в этом разделе должны присутствовать определения для каждого из видимых извне (public) предикатов и конструкторов, т.е. для ''new'', ''getName'' и ''setName''. В разделе '''implementation''' можно, кроме того, объявить и определить дополнительные локальные элементы, доступ к которым возможен только из самого раздела '''implementation'''. Так, класс содержит объявление факта-переменной ''name'', который будет использоваться для хранения имени персоны.
Как видно, в этом разделе должны присутствовать определения для каждого из видимых извне (public) предикатов и конструкторов, т.е. для ''new'', ''getName'' и ''setName''. В разделе '''implement''' можно, кроме того, объявить и определить дополнительные локальные элементы, доступ к которым возможен только из самого раздела '''implement'''. Так, класс содержит объявление факта-переменной ''name'', который будет использоваться для хранения имени персоны.


Каждый из объектов при создании получает свою собственную реализацию факта-переменной. Выше приведенные клозы будут ссылаться только на конкретную реализацию факта-переменной внутри объекта. Мы говорим, что этот предикат является объектным предикатом (object predicate), а факт является объектным фактом (object fact).
Каждый из объектов при создании получает свою собственную реализацию факта-переменной. Выше приведенные клозы будут ссылаться только на конкретную реализацию факта-переменной внутри объекта. Мы говорим, что этот предикат является объектным предикатом (object predicate), а факт является объектным фактом (object fact).


==Элементы класса==
==Элементы класса==
Класс может иметь элементы, общие для всех объектов этого класса. Расширим пример, добавив в него код, который позволяет подсчитывать количество созданных объектов класса '''''person'''''. Счетчик будет увеличиваться каждый раз, когда создается новый объект, и никогда не будет уменьшаться.
Класс может иметь элементы, общие для всех объектов этого класса. Расширим пример, добавив в него код, который позволяет подсчитывать количество созданных объектов класса ''person''. Счетчик будет увеличиваться каждый раз, когда создается новый объект, и никогда не будет уменьшаться.
Расширим объявление класса, добавив предикат ''getCreatedCount'' (получить значение счетчика созданий), который сможет возвращать текущее значение счетчика:
Расширим объявление класса, добавив предикат ''getCreatedCount'' (получить значение счетчика созданий), который сможет возвращать текущее значение счетчика:
<vip>
<vip>
Строка 67: Строка 64:
Отметим, что видимый извне предикат класса, объявлен в объявлении класса, тогда как видимый извне объектный предикат объявлен в интерфейсе. Из этого правила нет исключений; невозможно объявить объектный предикат в объявлении класса, и невозможно объявить предикат класса в разделе интерфейса.
Отметим, что видимый извне предикат класса, объявлен в объявлении класса, тогда как видимый извне объектный предикат объявлен в интерфейсе. Из этого правила нет исключений; невозможно объявить объектный предикат в объявлении класса, и невозможно объявить предикат класса в разделе интерфейса.


После объявления, каждый предикат должен быть определен в разделе '''implementation''' класса. То же самое нужно сделать для факта, который будет хранить счетчик. Этот факт должен быть фактом класса, т.е. быть общим для всех объектов. В разделе '''implementation''' класса можно  объявить локальные (private) как элементы объекта, так и элементы класса. Для объявления элементов класса нужно вставить ключевое слово '''class''' перед соответствующим объявлением. Например, имплементация класса person может быть представлена таким образом:
После объявления, каждый предикат должен быть определен в разделе '''implement''' (имплементация) класса. То же самое нужно сделать для факта, который будет хранить счетчик. Этот факт должен быть фактом класса, т.е. быть общим для всех объектов. В разделе '''implement''' класса можно  объявить локальные (private) как элементы объекта, так и элементы класса. Для объявления элементов класса нужно вставить ключевое слово '''class''' перед соответствующим объявлением. Например, имплементация класса ''person'' может быть представлена таким образом:
<vip>implement person
<vip>implement person
class facts
class facts
Строка 90: Строка 87:
     name := Name.
     name := Name.
end implement person</vip>
end implement person</vip>
Здесь мы добавили факт класса ''createdCount'' и инициализировали его нулевым значением. Мы также добавил клоз для предиката ''getCreatedCount'', который возвращает текущее значение переменной ''createdCount''. И, наконец, мы добавили в конструктор код, который увеличивает ''createdCount''.
Здесь мы добавили факт класса ''createdCount'' и инициализировали его нулевым значением. Мы также добавил клоз для предиката ''getCreatedCount'', который возвращает текущее значение переменной ''createdCount''. И, наконец, мы добавили в текст клоза-конструктора строку, которая увеличивает ''createdCount'' на единицу.
Отметим, что в конструкторе два присваивания имеют похожий вид, но одно присваивание меняет состояние объекта (createdCount := createdCount+1), а другое (name := Name) меняет состояние класса.
 
Отметим, что в конструкторе два присваивания имеют похожий вид, но одно присваивание ''createdCount := createdCount+1'', оперирующее с фактом класса, меняет состояние класса, а другое - ''name := Name'', оперирующее с фактом объекта, меняет состояние объекта.
 
==Модули==
==Модули==
Это особые варианты классов, которые вообще не порождают объектов, поэтому они действуют, как модули, а не как классы. Класс, не конструирующий объекты (т.е., модуль), объявляется без указания объектного типа:
Это особые варианты классов, которые вообще не порождают объектов, поэтому они действуют, как модули, а не как классы. Класс, не конструирующий объекты (т.е., модуль), объявляется без указания объектного типа:
<vip>
<vip>
class io % no type here
class io % тип не указан!
predicates
predicates
   write : (string ToWrite).
   write : (string ToWrite).
Строка 102: Строка 101:
</vip>
</vip>
Такой класс не может порождать объекты.
Такой класс не может порождать объекты.
==Создание и доступ==
==Создание и доступ==
С использованием кода, приведенного выше, можно задать цель, в которой создается объект, который используется классом ''io'' (применение которого мы здесь обсуждать не будем) для выдачи на печать имени персоны.
С использованием кода, приведенного выше, можно задать цель, в которой создается объект, который используется классом ''io'' (применение которого мы здесь обсуждать не будем) для выдачи на печать имени персоны.
Строка 114: Строка 114:


В последней строке происходит вызов предикат ''write'' класса ''io'' с передачей ему имени Name.
В последней строке происходит вызов предикат ''write'' класса ''io'' с передачей ему имени Name.


Отметим, что на имена классов ссылаются с помощью символа ‘::’, например, ''person::new(...)''. А объектные предикаты ссылаются на объекты с использованием символа ‘:’, напрмер, ''P:getName''.
Отметим, что на имена классов ссылаются с помощью символа ‘::’, например, ''person::new(...)''. А объектные предикаты ссылаются на объекты с использованием символа ‘:’, напрмер, ''P:getName''.
Строка 121: Строка 120:
==Интерфейсы как объектные типы==
==Интерфейсы как объектные типы==
Как уже говорилось, интерфейсы являются объектными типами. Это следует понимать буквально, вы можете использовать интерфейсы везде, где используются обычные необъектные типы. Например, в объявлении предиката:
Как уже говорилось, интерфейсы являются объектными типами. Это следует понимать буквально, вы можете использовать интерфейсы везде, где используются обычные необъектные типы. Например, в объявлении предиката:
<vip>class mail
predicates
    sendMessage : (person Recipient, string Message).
end class mail</vip>
Предикат mail::sendMessage имеет типы аргументов: person (т.е., имя интерфейса) и string.
Предикат mail::sendMessage имеет типы аргументов: person (т.е., имя интерфейса) и string.


==Многократное применение==
==Разнообразие имплементаций==
Можно создать несколько различных классов, которые могут создавать одинаковые объекты person. Вы просто объявляете классы с разделами implementation, которые содержат свои конструкторы объектов person. Разделы implementation могут быть различными. Например, можно создать класс, который сохраняет персоны в базе данных, а не в факт-переменной. Вот, объявление такого класса:
Можно создать несколько различных классов, которые могут создавать одинаковые объекты ''person''. Вы просто объявляете классы с разделами '''implement''', которые содержат свои конструкторы объектов ''person''. Разделы '''implement''' могут быть различными. Например, можно создать класс, который сохраняет персоны в базе данных, а не в факт-переменной. Вот, объявление такого класса:
 
<vip>class personInDB : person
В этом руководстве не рассматриваются конкретные реализации, но следующий код демонстрирует, что объекты одинаковых типов могут иметь различные разделы implement.
constructors
  new : (string DatabaseName, string Name).
end class personInDB</vip>
В этом руководстве не рассматриваются конкретные реализации, но следующий код демонстрирует, что объекты одинаковых типов могут иметь различные разделы '''implement'''.
<vip>
<vip>
implement personInDB
implement personInDB
Строка 145: Строка 151:
</vip>
</vip>
Обратите внимание, что различается не только внутреннее поведение объекта, но и внутреннее состояние также имеет разную структуру и содержание.
Обратите внимание, что различается не только внутреннее поведение объекта, но и внутреннее состояние также имеет разную структуру и содержание.
==Понятие полиморфизма==
 
Объекты одного типа могут использоваться в одинаковых контекстах, не обращая внимание, на различия в разделах implement. Можно, например, послать сообщение некоей персоне, используя почтовый класс, объявленный заранее, не обращая внимание, на то, каким конструктором объект person был создан: person or personInDB:
==Категорированный полиморфизм==
<vpi>
Объекты одного типа могут использоваться в одинаковых контекстах, независимо от различия в разделах '''implement'''. Можно, например, послать сообщение некоей персоне, используя почтовый класс, объявленный заранее, не обращая внимание, на то, каким конструктором объект ''person'' был создан: ''person'' или ''personInDB'':
<vip>
goal
goal
   P1 = person::new("John"),
   P1 = person::new("John"),
Строка 154: Строка 161:
   mail::sendMessage(P2, "Hi Paul, ...").
   mail::sendMessage(P2, "Hi Paul, ...").
</vip>
</vip>
Это поведение известно, как категорирование: в некоторых контекстах, объекты, сконструированные одним классом, используются наравне с объектами, сконструированными другим классом, если оба объекта имеют один и тот же тип, который требует контекст.
Это поведение известно, как категорирование: в некоторых контекстах, объекты, созданные с помощью одного классом, используются наравне с объектами, созданными с помощью другого класса, если оба объекта имеют один и тот же тип, который требуется контекстом.
Отметим, что предикат mail::sendMessage принимает объекты любого класса person, поэтому этот предикат является, в определенном смысле, полиморфным.
 
==Поддержка (Расширение типов)==
Отметим, что предикат ''mail::sendMessage'' принимает объекты любого класса ''person'', поэтому этот предикат является, в определенном смысле, полиморфным.
Пусть наша программа имеет дело со специальным сортом персон, а именно, пользователями программ. Пользователи являются персонами с именами и, кроме того, они имеют пароли. Создадим новый интерфейс (тип) объекта для пользователей, который бы устанавливал, что пользователь является персоной с паролем. Для этого используем поддерживающую квалификацию:
 
==Supports (поддерживает) - как расширение типов==
Пусть наша программа имеет дело со специфическими персонами, а именно, - с пользователями программ. Пользователи являются персонами с именами и, кроме того, они имеют пароли. Создадим новый интерфейс (тип) объекта для пользователей, который бы устанавливал, что пользователь является персоной с паролем. Для этого используем квалификацию '''supports''':
<vip>
<vip>
interface user supports person
interface user supports person
Строка 165: Строка 174:
end interface user
end interface user
</vip>
</vip>
Интерфейс user поддерживается интерфейсом person. Отсюда следует, что:
Интерфейс ''user'' поддерживает интерфейс ''person''. Это означает, что:
* Объекты типа user будут снабжены предикатами, объявленными в интерфейсе с именем person, а именно, getName и setName.
* Объекты типа ''user'' будут исполнять также предикаты, объявленные в интерфейсе ''person'', а именно, ''getName'' и ''setName''.
* Объекты типа user являются одновременно объектами типа person и, поэтому, могут использоваться в тех же контекстах, что и объекты person.
* Объекты типа ''user'' являются одновременно объектами типа ''person'' и, поэтому, могут использоваться в тех же контекстах, что и объекты ''person''.


Допустим, что имеется некоторый класс user.
Допустим, что имеется некоторый класс ''user''.
Тогда объекты этого класса могут быть использованы предикатом mail::sendMessage:
<vip>class user : user
constructors
    new : (string Name, string Password).
end class user</vip>
Тогда объекты этого класса могут быть использованы предикатом ''mail::sendMessage'':
<vip>
<vip>
goal
goal
   P = user_class::new("Benny", "MyCatBobby"),
   P = user::new("Benny", "MyCatBobby"),
   mail::sendMessage(P, "Hi Benny, ...").
   mail::sendMessage(P, "Hi Benny, ...").
</vip>
</vip>
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
* Объекты данного типа являются также объектами всех типов, соответствующих поддерживаемым интерфейсам.
Интерфейс может поддерживать несколько других интерфейсов, это означает, что:
Квалификация '''supports''' порождает, таким образом, иерархию подтипов. Мы говорим, что ''user'' является подтипом ''person''.
* Объекты должны предоставлять все предикаты тем объектам, чьи интерфейсы поддерживаются.
* Объекты также имеют все типы поддерживаемых интерфейсов
Интерфейс может поддерживать один или более интерфейсов, которые, в свою очередь, могут поддерживать один или более интерфейсов и т.д. В этом случае:
* Объект должен предоставлять все предикаты для поддерживаемых, прямо или непрямо, интерфейсов.
* Объект имеет, одновременно, все типы объектов поддерживаемых прямо или непрямо интерфейсов.
Поддерживающая квалификация порождает иерархию подтипов. Мы говорим, что user является подтипом person.


==Объект: первичный тип==
==object - первичный тип==
Интерфейс, который неявно поддерживает все интерфейсы, является объектным интерфейсом. Объектный интерфейс является интерфейсом, не имеющим содержания, т.е. в нем нет объявленных предикатов. Объектный интерфейс поддерживает, прямо или непрямо, любой другой интерфейс, поэтому все объекты имеют тип объектного интерфейса. Объектный интерфейс является супертипом для любого объекта.
Интерфейс '''object''' неявно поддерживает все интерфейсы. Интерфейс '''object''' является интерфейсом, не имеющим содержания, т.е. в нем нет объявленных предикатов. Объектный интерфейс '''object''' поддерживает, прямо или непрямо, любой другой интерфейс, поэтому все объекты имеют тип объектного интерфейса '''object'''. Объектный интерфейс '''object''' является супертипом для любого объекта.


==Наследование==
==Наследование==
Для применения класса user, можно использовать свойства одного из наших классов person. Класс user похож на класс person, за исключением того, что user имеет дело еще и с паролями. Хотелось бы, чтобы наш класс user наследовал раздел implement из класса person. Это можно сделать с помощью следующего кода:
Для применения класса ''user'', можно использовать свойства одного из наших классов ''person''. Класс ''user'' похож на класс ''person'', за исключением того, что ''user'' имеет дело еще и с паролями. Хотелось бы, чтобы наш класс ''user'' наследовал раздел '''implement''' из класса ''person''. Это можно сделать с помощью квалификации '''inherits''', как показано ниже:
<vip>implement user
    inherits person


Раздел implement подтверждает, что этот класс наследует (inherits) свойства класса person. Отсюда следует, что:
facts
* Объект person встраивается в каждый сконструированный объект user.
    password : string.
* Все предикаты, объявленные в интерфейсе person, могут быть наследованы из класса person в класс user.
clauses
Когда наследуется какой-нибудь предикат, определять его в разделе implement не нужно, т.к. наследуется определение предиката из раздела implement наследуемого класса.
    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</vip>
Раздел '''implement''' подтверждает, что этот класс наследует ('''inherits''') свойства класса ''person''. Отсюда следует, что:
* Объект ''person'' встраивается в каждый созданный объект ''user''.
* Все предикаты, объявленные в интерфейсе ''person'', могут быть наследованы из класса ''person'' в класс ''user''.
Когда наследуется какой-нибудь предикат, определять его в разделе '''implement''' не нужно, т.к. наследуется определение предиката из раздела '''implement''' наследуемого класса.


Можно получить тот же результат с помощью следующего кода (клозы для предикатов, обрабатывающие пароли остаются теми же):
Можно получить тот же результат с помощью следующего кода (клозы для предикатов, обрабатывающие пароли остаются теми же):
Строка 202: Строка 226:
implement user
implement user
facts
facts
person : person.
  person : person.
password : string.
  password : string.
clauses
clauses
 
  new(Name, Password) :-
new(Name, Password) :-
    person := person::new(Name),
 
    password := Password.
person := person_class::new(Name),
 
password := Password.
 
clauses  
clauses  
 
  getName() = person:getName().
getName() = person:getName().
clauses
  setName(Name) :-
    person:setName(Name).
end implement user
</vip>
Действие этого кода похоже на действие предыдущего фрагмента, но размер текста увеличился.
В этом фрагменте наследования из класса ''person'' не происходит. Вместо этого создан объект ''person'' и он сохранен в одноименном факте-переменной. Вместо наследования кода ''getName'' и ''setName'' они просто переопределены и теперь обращаются за решением к объекту из факт-переменной. Такие обращения за решением можно сократить с помощью ключевого слова '''delegate'''. Код в этом случае сократится.
<vip>
implement user
  delegate interface person to person
facts
  person : person:=erroneous.
  password : string.
clauses
clauses
 
  new(Name, Password) :-
setName(Name) :- person:setName(Name).
    person := person::new(Name),
...
    password := Password.
end implement user_class
end implement user_class
</vip>
</vip>
В этом фрагменте наследования из класса person не происходит. Вместо этого создан объект person и он сохранен в факт-переменной. Вместо наследования кода getName и setName они просто переопределены и теперь обращаются за решением к объекту из факт-переменной. Такие обращения за решением можно сократить с помощью ключевого слова delegate.
Функционирование то же, но:
Действие этого кода похоже на действие предыдущего фрагмента, но есть заметные различия:
* Вызовы предикатов, относящихся к объекту ''person'' транзитом передаются в него
* Прежде всего, увеличился размер текста.
* Mожно динамически изменить значение в факт-переменной на другой объект, например, на объект класса ''personInDB'' простым присваиванием нового объекта факту-переменной.
* Объект person не встраивается в объект user, а, вместо этого, оюъект ссылается на него. Кроме того, увеличился размер занятой памяти.
 
* В последнем случае, можно динамически изменить значение в факт-переменной на другой объект, например, на объект класса personInDB простым присваиванием нового объекта факт-переменной.
VIP обрабатывает такие непрямые обращения довольно эффективно, но в случае наследования обработка все же происходит гораздо быстрее.


Следует отметить, что второй вариант раздела implement имеет дополнительный непрямой вызов. VIP обрабатывает такие непрямые обращения довольно эффективно, но в случае наследования обработка все происходит гораздо быстрее.
VIP дает возможность многократного наследования, т.е., вы можете одновременно наследовать многие классы.
VIP дает возможность многократного наследования, т.е., вы можете одновременно наследовать многие классы.


==Заключение==
==Заключение==
Вы познакомились с самыми основными концепциями объектной системы в VIP. В ней имеются и другие интересные особенности, о которых здесь не было упомянуто, например,:
Вы познакомились с самыми основными концепциями объектной системы в VIP. В ней имеются и другие интересные особенности, о которых здесь не было упомянуто, например,:
Объекты могут поддерживать добавочные интерфейсы в разделе implement.
* Объекты могут поддерживать добавочные интерфейсы в разделе '''implement'''.
Предикаты завершения, освобождающие занятую память.
* Предикаты завершения, освобождающие занятую память.
и т.д.
* и т.д.
==Ссылки==
[[en:Introduction_to_Classes_and_Objects]]
[[en:Introduction_to_Classes_and_Objects]]
[[Категория:VipРуководства]]
[[Категория:VipРуководства]]
[[Категория:VipLanguage]]

Текущая версия на 06:46, 24 октября 2007

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

Ссылки