Языки CLIPS и CLOS. Множественное наследование в CLOS и CLIPS.
Появление языка Common LISP было связано с попыткой стандартизировать многочисленные диалекты LISP и создать устраивающую большинство пользователей версию этого языка. Развитие объектно-ориентированного подхода в программировании привело к разработке объектно-ориентированной версии LISP – Common LISP Object System (CLOS), о которой и пойдет речь в этом разделе. Разработчики CLOS включили в свою систему поддержку всех новшеств, ранее хорошо себя зарекомендовавших в языках FLAVORS и LOOPS, таких как множественное наследование, объединение методов и структура метаклассов.
Объектно-ориентированная версия CLIPS, язык COOL, очень близок к CLOS, что мы и продемонстрируем на примерах в этом разделе.
Механизм множественного наследования в языках CLOS и CLIPS работает практически так же, как и в языке LOOPS. Порядок, в котором базовые классы перечислены в определении подкласса, задает и порядок приоритетов наследования данных и процедур. Кроме того, существует правило, в соответствии с которым определение процедуры или свойства, сделанное в классе, всегда имеет приоритет перед унаследованными от суперклассов. Эти соглашения позволяют разрешить проблему неоднозначности при множественном наследовании путем формирования списка предшествования классов.
Рассмотрим фрагмент программы на языке CLIPS, представленный в листинге 7.1. Этот фрагмент описывает "Алмаз Никсона", о котором шла речь в главе 6. Класс person определен как объявленный пользователем, классы quaker и republican – производные от person, a republican-quaker – производный как от quaker, так и от republican. Класс USER является системным абстрактным классом, т.е. может быть использован только для создания подклассов. Если планируется создавать экземпляры любого класса, производного от USER, то этот класс нужно объявлять с квалификатором concrete, как это и сделано при объявлении класса republican-quaker.
Листинг 7.1. Объявление классов на языке CLIPS.
(defclass person (is-a USER) (defclass quaker (is-a person) (defclass republican (is-a person) (defclass republican-quaker (is-a republican quaker) (role concrete)
Список предшествования классов для класса republican-quaker будет иметь вид:
(republican-quaker republican,quaker person).
Список формируется в результате прослеживания графа связей системы классов, который неявно представлен слотами is-a в определениях классов.
Роль списка предшествования классов становится ясной при разработке обработчика событий для производного класса. Определим поведение классов quaker и republican как "голубей" и "ястребов" соответственно:
(defmessage-handler quaker speak () (printout t crlf "Peace") ) (defmessage-handler republican speak () (printout t crlf "War"))
Сформируем экземпляр класса republican-quaker:
(definstances people (richard of republican-quaker))
Теперь загрузим все это в исполняющую систему CLIPS и введем запрос к экземпляру Richard:
(send [richard] speak)
В ответ интерпретатор выведет "War" (война). Оказывается, что "ястребиный" характер республиканцев возобладал у экземпляра richard, поскольку в списке предшествования классов republican стоит раньше, чем quaker. Изменим порядок перечисления этих классов в определении republican-quaker:
(defclass republican-quaker (is-a quaker republican) (role concrete))
Теперь в характере экземпляра Richard миролюбие квакеров будет доминировать. Ничего не изменится в поведении экземпляра и в том случае, если добавить обработчик сообщения в класс person:
(defmessage-handler person speak () (printout t crlf "Beer"))
Эта реализация метода speak перекрывается другими, поскольку класс находится в списке предшествования на последнем месте.
Слоты данных в языке COOL также поддерживают фацеты, т.е. свойства, ответственные за доступ к слотам в процессе работы программы. Например, существует фацет visibility (видимость), который определяет, какие другие классы могут обратиться к слоту. Значение private означает, что только обработчик сообщения данного класса может получить доступ к данным, а значение public позволяет это сделать также обработчикам сообщений производных классов и суперклассов.
Другие фацеты позволяют реализовать следующие возможности:
- автоматическое определение функций доступа и присвоения значений слотам;
- хранение данных, к которым возможен доступ со стороны всех экземпляров класса, аналогично статическим членам классов в языке C++.