Опубликован: 23.10.2005 | Доступ: свободный | Студентов: 4086 / 201 | Оценка: 4.44 / 4.19 | Длительность: 33:04:00
Специальности: Программист
Дополнительный материал 2:

Универсальность и (versus) наследование

< Дополнительный материал 1 || Дополнительный материал 2: 123456

Сочетание универсальности и наследования

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

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

Закрепленные объявления решают вторую проблему. (Читатель, знакомый с лекцией 17 курса "Основы объектно-ориентированного программирования", где детально обсуждались проблемы типизации, мог заметить важность проблемы проверки правильности системы, но эти вопросы исчезнут в окончательном варианте, представленном ниже.)

Давайте посмотрим, как можно решить первую проблему введением (по сути, повторным введением) подходящей формы универсальности.

Неограниченная универсальность

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

class C [G, H, ...] ...

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

x: C [DEVICE, RING_ELEMENT, ...]

Такой подход непосредственно применим к классу очереди, который может быть просто определен как:

indexing
    description: "Очередь, реализованная массивом"
class QUEUE [G] creation
    ... Все остальное как ранее, но удалив объявление item_anchor
    и заменив все вхождения типа like item_anchor на G ...
end

Мы избавились от класса QUEUABLE так же, как и от INTEGER_QUEUABLE и всех других потомков. Для получения очереди целых будем просто использовать тип QUEUE [INTEGER], непосредственно манипулируя целыми, а не обертывающими их объектами.

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

Обеспечение неограниченной универсальности

Наряду с наследованием желательно обеспечить специальную нотацию для объявления классов как универсальных (неограниченных).

Ограниченная универсальность

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

class MATRIX [G] feature
    anchor: RING_ELEMENT [G]
    ...Другие компоненты как ранее ...
end

класс, задающий элементы кольца, теперь объявляется так:

deferred class RING_ELEMENT [G] feature
    item: G
    put (new: G) is do item := new end
    ... Другие компоненты как ранее...
end

Использование одного и того же родового параметра для двух связанных классов, RING_ELEMENT и MATRIX, гарантирует согласованность типов: все элементы данной матрицы будут принадлежать типу RING_ELEMENT [G] с одним и тем же G.

Аналогично можно создать универсальный класс COMPARABLE:

deferred class COMPARABLE [G] feature
    item: G
    put (new: G) is do item := new end
    ...Другие компоненты (infix "<=", minimum) как ранее ...
end

Компоненты класса ( infix "<=", minimum ) представляют ограничения (программы в форме Ada). Ранее заданные потомки становятся совершенно простыми:

class INTEGER_COMPARABLE inherit
    COMPARABLE [INTEGER]
creation
    put
end

(Заметьте, это полностью заданный класс, а не его схема, в которую следует добавлять компоненты!) Этот же прием непосредственно применим ко всем другим вариантам, таким как STRING_COMPARABLE.

Эта простая в применении техника приводит к следующему принципу эмуляции:

Эмуляция ограниченной универсальности (2)

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

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

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

Эта нотация, как показано в предыдущих лекциях, позволяет задавать ограниченную универсальность в виде:

class MATRIX [G -> RING_ELEMENT] ...
class SORTABLE_LIST [G -> COMPARABLE] ...

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

  • Исчезает необходимость, подобно Ada, использовать программы в качестве родовых параметров. Только типы могут быть родовыми параметрами, что приводит к нужному согласованию и просто для понимания.
  • Исчезает необходимость в специальных обертывающих классах и объектах. Если необходима матрица целых чисел, достаточно объявить MATRIX [INTEGER] и использовать свободно целые при работе с ее элементами. Если необходим список строк с возможностью их сортировки, достаточно объявить его как SORTABLE_LIST [STRING].

Напоминаю семантику: теперь родовой параметр G не представляет произвольный тип - он должен удовлетворять ограничениям, будучи потомком определенного класса. Родовое порождение, такое как MATRIX [T], будет корректным, если и только если T - такой тип. Это верно для INTEGER, но не выполняется для типа STRING. Аналогично, STRING является наследником COMPARABLE и приемлем в качестве фактического параметра для класса SORTABLE_LIST, но это не верно для класса COMPLEX (комплексных чисел), для которых не задано отношение полного порядка. Символ -> был выбран для напоминания о стрелках в диаграммах наследования.

Обеспечение ограниченной универсальности

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

И, как последнюю деталь, напомним, что в этой схеме ограниченная универсальность становится основным свойством, а неограниченная представляется ее частным случаем. Например, QUEUE [G], теперь понимается как сокращение записи QUEUE [G-> ANY], где ANY означает класс, служащий предком для всех классов, включая классы, создаваемые разработчиком. Как следствие, теперь точно определяются операции, применимые к G: они наследуются от ANY, применимы ко всем классам, включая общецелевые компоненты, такие как clone, print и equal.

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

< Дополнительный материал 1 || Дополнительный материал 2: 123456