Основы объектно-ориентированного программирования - [266]

Шрифт
Интервал

Остерегайтесь полиморфных кэтколлов!

Правило Системной Корректности пессимистично: в целях упрощения оно отвергает и вполне безопасные комбинации инструкций. Как ни парадоксально, но последний вариант решения мы построим на основе еще более пессимистического правила. Естественно, это поднимет вопрос о том, насколько реалистичным будет наш результат.

Назад, в Ялту

Суть решения Кэтколл (Catcall), - смысл этого понятия мы поясним позднее, - в возвращении к духу Ялтинских соглашений, разделяющих мир на полиморфный и ковариантный (и спутник ковариантности - скрытие потомков), но без необходимости обладания бесконечной мудростью.

Как и прежде, сузим вопрос о ковариантности до двух операций. В нашем главном примере это полиморфное присваивание: s := b, и вызов ковариантной подпрограммы: s.share (g). Анализируя, кто же является истинным виновником нарушений, исключим аргумент g из числа подозреваемых. Любой аргумент, имеющий тип SKIER или порожденный от него, нам не подходит ввиду полиморфизма s и ковариантности share. А потому если статически описать сущность other как SKIER и динамически присоединить к объекту SKIER, то вызов s.share (other) статически создаст впечатление идеального варианта, но приведет к нарушению типов, если полиморфно присвоить s значение b.

Фундаментальная проблема в том, что мы пытаемся использовать s двумя несовместимыми способами: как полиморфную сущность и как цель вызова ковариантной подпрограммы. (В другом нашем примере проблема состоит в использовании p как полиморфной сущности и как цели вызова подпрограммы потомка, скрывающего компонент add_vertex.)

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

Одно правило и несколько определений

Правило типов для решения Кэтколл имеет простую формулировку:

Правило типов для Кэтколл

Полиморфные кэтколлы некорректны.

В его основе - столь же простые определения. Прежде всего, полиморфная сущность:

Определение: полиморфная сущность

Сущность x ссылочного (не развернутого) типа полиморфна, если она обладает одним из следующих свойств:

1 Встречается в присваивании x := y, где сущность y имеет иной тип или по рекурсии полиморфна.

2 Встречается в инструкциях создания create {OTHER_TYPE} x, где OTHER_TYPE не является типом, указанным в объявлении x.

3 Является формальным аргументом подпрограммы.

4 Является внешней функцией.

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

В наших примерах лыжник s и многоугольник p - полиморфны по правилу (1). Первому из них присваивается объект BOY b, второму - объект RECTANGLE r.

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

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

Как было сказано при обсуждении наследования, подобный анализ может также представлять ценность при оптимизации кода.

Вызовы, как и сущности, могут быть полиморфными:

Определение: полиморфный вызов

Вызов является полиморфным, если его цель полиморфна.

Оба вызова в наших примерах полиморфны: s.share (g) ввиду полиморфизма s, p.add_ vertex (...) ввиду полиморфизма p. Согласно определению, только квалифицированные вызовы могут быть полиморфны. (Придав неквалифицированному вызову