Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP / books / Osnovi objektno-orientirovannogo programmirovaniya.pdf
Скачиваний:
63
Добавлен:
03.03.2016
Размер:
9.04 Mб
Скачать

операций, с классическими подходами. В Паскале или Аде можно использовать тип записи с вариантами

type FIGURE = record

"Общие поля"

case figtype: (polygon, rectangle, triangle, circle,...) of polygon: (vertices: LIST_OF_POINTS; count: INTEGER); rectangle: (side1, side2: REAL;...);

...

end

чтобы определить различные виды фигур. Но это означает, что всякая программа, которая должна работать с фигурами (поворачивать и т.п.) должна проводить разбор возможных случаев:

case f.figure_type of

polygon: ...

circle: ...

...

end

В случае таблиц процедура search должна была бы использовать ту же структуру. Неприятность состоит в том, что эти процедуры должны обладать чересчур большими знаниями о будущем всей системы: они должны точно знать, какие типы фигур в ней допускаются. Любое добавление нового типа или изменение существующего будет затрагивать каждую процедуру.

Ne sutor ultra crepidam, (для сапожника ничего сверх сандалий) - это принцип разработки ПО: процедуре поворота не требуется знать полный список типов фигур. Ей должно хватать информации необходимой для выполнения своей работы: поворота некоторых видов фигур.

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

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

Независимость от представления

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

present := has (x, t)

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

present := t.has (x)

найдет во время выполнения подходящую версию процедуры has. С помощью динамического связывания достигается то, что было невозможно получить с помощью перегрузки и универсальности: клиент может запросить некоторую операцию, а поддерживающая язык система автоматически найдет ее соответствующую реализацию.

Таким образом, объединение классов, наследования, переопределения, полиморфизма и динамического связывания дает прекрасные ответы на вопросы, поставленные в начале этой книги: требования повторного использования, критерии, принципы и правила модульности.

Парадокс расширения-специализации

Наследование иногда рассматривается как расширение, а иногда как специализация. Хотя эти два толкования как будто противоречат друг другу, оба они истинны - но с разных точек зрения.

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

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

|>Здесь мы говорим о реализуемых компонентах, а не о предлагаемых (клиентам) службах, потому что при соединении скрытия информации с наследованием, как мы увидим, B может скрыть от клиентов некоторые из компонентов, в то время как A их экспортировал своим клиентам. |

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

Парадокс расширения-специализации - это одна из причин для устранения термина

"подкласс", предполагающего понятие "подмножество". Другой, уже отмеченной, является встречающееся в литературе сбивающее с толку использование термина "подкласс" для обозначения как прямого, так и непрямого наследования. Эти проблемы не возникают при использовании точно определенных терминов: наследник, потомок и собственный потомок и

двойственных к ним терминов: родитель, предок и собственный предок.

Соседние файлы в папке books