Теперь остается только разработать обработчик события, который будет использовать функцию для установки нужного значения в слот area объекта square-one. 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Теперь остается только разработать обработчик события, который будет использовать функцию для установки нужного значения в слот area объекта square-one.



5. Метод, который был реализован в предыдущем упражнении, хорош для работы с квадратами, но с его помощью нельзя решить аналогичную проблему при работе с другими четырехугольниками, представленными в нашей иерархии,— прямоугольниками, параллелограммами и трапециями. Теперь, когда вы знаете, как сформировать слоты и обработчики событий, пользуясь средствами языка CLIPS, попытайтесь решить и эту проблему. Для этого вам потребуется передавать объекту любого класса, расположенного в иерархии ниже узла четырехугольник, сообщение, в ответ на которое соответствующий обработчик должен извлечь данные из слотов, представляющих отдельные исходные параметры формы фигуры (длины сторон, высота и т.д.), и обрабатывать их по формуле, специфичной для фигур каждого вида. Постарайтесь найти такое решение, которое позволяло "бы обрабатывать различные фигуры по возможности единообразно. Учтите, что подклассы могут наследовать и слоты, и обработчики сообщений от своих суперклассов (предшественников).


ГЛАВА 7. Объектно-ориентированное программирование

Язык KRL

Языки LOOPS и FLAVORS

Передача сообщений

Проблема наложения методов

Метаклассы

Языки CLIPS и CLOS

Множественное наследование в CLOS и CLIPS

Наложение методов в CLOS и CLIPS

Метаклассы в CLOS и CLIPS

7.4. Множественное наследование в C++

Объектно-ориентированный анализ и конструирование экспертных систем

Рекомендуемая литература

Упражнения

ГЛАВА 7. Объектно-ориентированное программирование

Язык KRL

Языки LOOPS и FLAVORS

Языки CLIPS и CLOS

7.4. Множественное наследование в C++

Объектно-ориентированный анализ и конструирование экспертных систем

Рекомендуемая литература

Упражнения

За последние 20 лет было разработано довольно много языков для представления знаний, причем большинство из них можно отнести к классу объектно-ориентированных. Как и в случае с использованием концепции фреймов, основная идея состоит в том, чтобы заключить данные и связанные с ними процедуры в некие структуры, объединенные механизмом наследования. Отличие от формализмов, описанных в предыдущей главе, состоит в том, что процедуры могут наследоваться (и комбинироваться) точно так же, как и данные, а объекты могут взаимодействовать друг с другом напрямую или посредством специальных протоколов обмена сообщениями.

Сначала читатель вкратце познакомится с одним из предшественников современных программных средств — системой KRL (сокращение от Knowledge Representation Language — язык представления знаний). Потом будет показано, как в процессе эволюции в последующих разработках были преодолены некоторые сложности, присущие этому стилю программирования. Читатель познакомится с системами FLAVORS, LOOPS и более современной системой CLOS (Common LISP Object System — объектная система на базе обычного LISP). В конце главы описывается, как объектно-ориентированный подход реализован в языке CLIPS, и рассмотрены достоинства и недостатки использования для представления знаний объектно-ориентированных языков общего назначения, таких как C++.

В данной главе мы вновь затронем некоторые вопросы, рассмотренные в предыдущих главах, в частности вопрос о наследовании, но уделим ему гораздо больше внимания. Независимо от того, какой конкретный язык будет обсуждаться в том или ином разделе, во всех представленных примерах используется либо язык COOL (CLIPS Object Oriented language — объектно-ориентированная версия языка CLIPS), либо C++. Разделы, в которых детально изложены технические подробности функционирования конкретных программных средств (они помечены крестиком), можно при желании опустить. Большинство примеров приведено во врезках. При первом чтении их также можно бегло просмотреть или опустить, что не помешает разобраться в основных темах главы.

Язык KRL

В языке KRL впервые была сделана попытка собрать воедино результаты выполненных ранее исследований о структурировании элементов знаний и реализовать их в виде единой системы [Bobrow and Winograd, 1977]. Создание системы преследовало не только теоретические цели, но и имело достаточно четкую практическую направленность. В качестве "строительных блоков" системы использованы так называемые "концептуальные объекты", которые были сходны с фреймами, предложенными Минским, в том, что представляют прототипы и связанные с ними свойства. Основную идею авторы так изложили в опубликованной в 1977 году статье:

"...анализ последствий объектно-центрической факторизации знаний в противовес более общей факторизации, предполагающей структурирование знаний в виде набора фактов, каждый из которых ссылается на один или несколько объектов".

Такая ориентация повлекла за собой создание декларативного языка, основанного на описаниях, в которых концептуальные объекты рассматриваются не изолированно, а в совокупности с другими объектами-прототипами. Фундаментальное предположение состояло в том, что то, какие свойства некоторого объекта знаний оказываются существенными, представляющими интерес и т.д., зависит от точки зрения на объект и цели решаемой задачи. Например, если вам нужно сыграть музыкальную пьесу на пианино, то вас интересуют такие свойства этого объекта, как качество звучания, настройки и т.п. А вот грузчику более интересны такие свойства этого музыкального инструмента, как вес и габариты.

В этом свете описание новой сущности можно рассматривать как процесс сравнения ее с ранее описанными: нужно указать, на какие из известных объектов похож новый и чем именно, а в чем от них отличается. Так, мини-фургон очень похож на легковой автомобиль, но отличается от последнего отсутствием сидений для пассажиров и окон в задней части. Другими словами, полный набор понятий можно определить в терминах друг друга, а не в терминах более компактного множества примитивных идей. Сложность с использованием примитивов в представлении семантики состоит в том, что вряд ли когда-нибудь удастся прийти к единому мнению о том, что же представляют собой такие примитивные понятия и как их следует комбинировать при формировании более сложной идеи (с некоторыми соображениями на сей счет читатель может ознакомиться в работах [Schank, 1975] и [Schank andAbelson, 1977]).

В основе процедурных свойств языка KRL также лежат наиболее распространенные программные методы, предполагающие подключение процедур общего вида к классам объектов данных. Бобров и Виноград объединили этот вид подключения процедур со структурой фрейма и позволили подклассам наследовать как процедуры, так и данные своего суперкласса.

Разработчики языка предположили, что наследование процедур позволит программировать в терминах родовых операций, детали реализации которых конкретизируются по-разному для объектов разных классов. Так же, как абстрактные типы данных позволяют программисту забыть о деталях хранения конкретных данных в машине, так и родовые операции позволяют на определенной стадии не принимать во внимание детали реализации однотипных операций в каждом конкретном случае. Пример поможет вам четче представить смысл этой идеи.

Предположим, вас назначили Верховным главнокомандующим при проведении военной операции, в которой принимают участие различные рода войск. Отданные в ваше распоряжение танки, корабли и самолеты ждут приказа атаковать. Получив такой приказ, каждый из родов войск будет действовать по-своему: самолеты начнут бомбить, корабли — выпускать ракеты и т.д. Но поведение каждого из родов войск — это частная реализация общей концепции наступательных военных действий. Как главнокомандующего, вас мало интересуют детали выполнения приказа: на какие рычаги нажимать, какие переключатели включать, — все это должно быть определено на более низких уровнях.

Возвращаясь вновь к KRL, отметим, что идея, которая стоит за объектно-центрической организацией процедур, состоит в том, чтобы попытаться программно воспроизвести тот естественный стиль выработки суждений, который реализуется в мозге человека. В частности, предполагается, что управление логическим выводом реализуется на локальном уровне, в отличие от глобальных задач, ассоциирующихся, например, с автоматическим доказательством теорем. Другими словами, зная, как реализовать родовые операции, классы объектов будут обладать знаниями и о том, когда активизировать те многочисленные процедуры, к которым они имеют доступ.

Мы не затрагивали многих других аспектов языка KRL, например средств управления процессом или составления расписаний работ. Читателям, интересующимся этим языком, рекомендуем познакомиться с критическим анализом этого языка, который выполнили Ленерт и Уилкс [Lehnert and Wilks, 1979], и ответом разработчиков на эти критические замечания [Bobrow and Winograd, 1979]. Нельзя не отметить, что язык KRL явился тем локомотивом, который существенно подтолкнул исследования в области теории представления знаний и, в частности, способствовал появлению практических систем, о которых речь пойдет в следующем разделе.

Процедуры и объекты

На рис. 7,1 мы попытались схематически представить, в чем основная разница между процедурно- и объектно-ориентированным подходами в программировании.

Серые! эллипсы на схеме в левой части рисунка представляют процедуры, некоторые из которых напрямую обращаются к данным, хранящимся в файле или в базе данных. Зачерненный эллипс представляет процедуру самого верхнего уровня (в языке С — это процедура main). Эта функция вызывает другие функции, которые в конце концов вызывают функции самого нижнего уровня, выполняющие операции ввода/вывода.

На правой схеме объекты объединяют данные и процедуры работы с ними. Объекты организованы в виде одной или нескольких иерархических структур — деревьев или решеток. Утолщенный прямоугольник на схеме представляет базовый абстрактный класс. Экземпляры этих объектов взаимодействуют друг с другом, обмениваясь сообщениями, и таким образом образуются связи, ортогональные иерархии наследования.

Рис. 7.1. Процедурно- и объектно-ориентированные парадигмы программирования. Незаполненные фигуры представляют данные, а фигуры с заливкой—процедуры

Языки LOOPS и FLAVORS

Объектно-ориентированный стиль программирования идеально подходит для решения проблем, требующих детального представления объектов реального мира и динамических отношений между ними. Классическим примером применения данного подхода являются задачи моделирования. В таких программах компоненты сложной системы представляются структурами, инкапсулирующими и данные, и функции, моделирующие поведение соответствующих компонентов. Первым языком, в котором была реализована такая идея, стал SmallTalk [Goldberg andRobson, 1983].

Для задач искусственного интеллекта были разработаны языки LOOPS и FLAVORS, причем оба представляли собой объектно-ориентированные расширения языка LISP. Хотя в настоящее время эти языки практически не используются, реализованные в них базовые идеи унаследованы множеством языков представления знаний, появившихся позже. В частности, это можно сказать о языках CLOS (Common LISP Object System) и CLIPS. Ниже мы кратко опишем основные функциональные возможности языков LOOPS и FLAVORS и обратим ваше внимание на некоторые сложности, связанные с реализацией объектно-ориентированного стиля программирования.

Передача сообщений

Идея объектно-ориентированного программирования состоит в том, что программа строится вокруг множества объектов, каждый из которых обладает собственным набором функций (операций). Вместо того чтобы представлять объект пассивным набором данных, объектно-ориентированная система позволяет объекту играть более активную роль, в частности взаимодействовать с другими объектами, обмениваясь с ними сообщениями. В результате основной упор переносится с разработки общей управляющей структуры программы, которая ответственна за порядок обращения к функциям, на конструирование самих объектов, выяснение их ролей и создание протоколов взаимодействия объектов. Эти протоколы, по существу, определяют интерфейс между объектами. Если один объект должен взаимодействовать с другим, он должен вызывать функции в строгом соответствии с этим интерфейсом.

Объекты располагают собственными данными, которые играют ту же роль, что и слоты фреймов, собственным механизмом обновления этих данных и использования хранящейся в них информации. Помимо функций интерфейса, объекты располагают собственными, "приватными" функциями, которые, как правило, представляют собой реализацию определенной родовой операции применительно к данному объекту. Помимо данных, передаваемых в качестве аргументов родовой операции, такие функции используют и локальные данные объекта — аналоги слотов фрейма.

Предположим, мы определили объект, представляющий класс ship (корабль), и наделили его свойствами x-velocity (скорость по х) и y-velocity (скорость по у). Теперь можно создать экземпляр класса ship, назвать его Titanic и одновременно присвоить свойствам x-velocity и y-velocity нового экземпляра исходные значения. Практически нет никаких отличий между этой процедурой и процедурой создания нового экземпляра фрейма, рассмотренной в предыдущей главе.

Предположим теперь, что нам понадобилось определить процедуру speed, которая будет вычислять скорость судна на основании значений свойств x-velocity и у-velocity (скорость вычисляется как корень квадратный из суммы квадратов компонентов). Такая процедура будет принадлежать абстрактному типу данных, представляющему любые суда (в терминологии языка SmallTalk speed — это метод класса ships, а в терминологии C++ — функция-член класса ships.)

Идея состоит в том, чтобы закодировать в объекте (классе) не только декларативные знания о судах, но и процедурные, т.е. методы использования декларативных знаний. Для того чтобы активизировать процедуру вычисления скорости определенного судна, в частности "Титаника", нужно передать объекту Titanic сообщение, которое побудит его обратиться к ассоциированной процедуре в контексте данных о компонентах скорости именно этого объекта. Titanic — это экземпляр класса ships, от которого он унаследовал процедуру speed. Все это представляется довольно очевидным, но описанный механизм срабатывает только в случае, если соблюдаются следующие соглашения.

Во-первых, программа, разработанная в расчете на этот механизм, должна "учредить" и следовать в дальнейшем определенному протоколу или "контракту", определяющему способ взаимодействия между объектами. Другими словами, интерфейс обмена сообщениями между объектами должен быть досконально продуман, а правила этого интерфейса жестко соблюдаться. Лучше всего продемонстрировать эту мысль на примере.

Для того чтобы определить компоненту X текущего положения "Титаника", программа должна послать запрос объекту Titanic, который имел бы следующий смысл: "передай текущее значение координаты X". Как в объекте формируется это значение или как оно хранится — дело только самого объекта и никого более не касается. Ни объекты других классов, ни какие-либо другие компоненты программы этого не знают. Более того, внутренний механизм доступа к этой информации должен быть скрыт, чтобы никто не мог добраться к ней, минуя сам объект. Это соглашение принято называть инкапсуляцией.

Во-вторых, совершенно очевидна избыточность определения своего метода вычисления скорости для каждого класса объектов, которые обладают возможностью перемещаться в двумерной системе координат. Метод, который мы только что определили для класса судов, с таким же успехом может быть использован и для других движущихся объектов, поскольку вычисление скорости представляет собой родовую операцию. Поэтому имеет смысл связать этот метод с каким-нибудь суперклассом транспортных средств, производными от которого будут классы судов, автомобилей, троллейбусов и т.п. Все эти подклассы унаследуют родовую операцию у своего базового класса.

Такое наследование — это уже нечто большее, чем когнитивная экономия или наследование свойств. Выполнение родовых операций встраивается в механизм обмена сообщениями. Отсылка сообщения— это отнюдь не вызов определенной процедуры, поскольку вызывающий объект не знает, каким именно методом отреагирует на это сообщение объект-получатель, от кого он унаследует этот метод, и будет ли вообще задействован механизм наследования в данном конкретном случае. Вызывающему объекту известны лишь наименование операции и ее внешние по отношению к преемнику аргументы. Все остальное — заботы объекта-реципиента сообщения.



Поделиться:


Последнее изменение этой страницы: 2016-04-07; просмотров: 199; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.15.221.136 (0.017 с.)