Основы объектно-ориентированного программирования
Шрифт:
Заметим, что наряду с типом G и подпрограммами родовым параметром служит значение zero– нулевой элемент сложения. Типичное использования пакета:
В этом примере логическая операция or используется как сложение, and– умножение, а также задаются соответствующие значения для zero и unity. Подробнее мы обсудим этот пример в одной из следующих лекций курса.
Являясь решением для Ada, данный прием не применим в объектной среде. Основа ОО-подхода - приоритет типов данных над операциями при декомпозиции ПО, чьим следствием является отсутствие независимых операций. Всякая операция принадлежит некоторому типу данных, основанному на классе. Следовательно, возникшая "на пустом месте" функция, скажем, infix "+", не может быть фактическим родовым параметром, стоящим в одном ряду с типами INTEGER и BOOLEAN. То же касается и значений, таких как zero и unity, обязанных знать свое место - быть компонентами класса - вполне респектабельными членами ОО-сообщества.
Ограничение родового параметра
Эти наблюдения дают решение. Мы должны оперировать исключительно терминами классов и типов.
Потребуем, чтобы любой фактический параметр, используемый классом VECTOR (в других примерах по аналогии), был типом, поставляемым с множеством операций: infix "+", zero для инициализации суммы и т.д. Владея наследованием, мы знаем, как снабдить тип нужными операциями, - нужно просто сделать его потомком класса, отложенного или эффективного, обладающего этими операциями.
Синтаксически это выглядит так:
где CONSTRAINING_TYPE– произвольный тип, именуемый родовым ограничением (generic constraint). Символ – > обозначает стрелку на диаграммах наследования. Результат этого объявления в том, что:
[x]. в роли фактических родовых параметров могут выступать лишь типы, совместимые с CONSTRAINING_TYPE;
[x]. в классе C над сущностью типа G допускаются только те операции, которые допускаются над сущностью CONSTRAINING_TYPE, другими словами, представляющими собой компоненты базового класса этого типа.
Какое родовое ограничение использовать для класса VECTOR? Обсуждая множественное наследование, мы ввели в рассмотрение NUMERIC– класс объектов, допускающих базисные арифметические операции: сложение и умножение с нулем и единицей (лежащая в его основе математическая структура называется кольцом). Эта модель кажется вполне уместной, хотя нам необходимо пока только сложение. Соответственно, класс будет описан так:
После чего ранее некорректная конструкция в теле цикла
становится допустимой, поскольку item (i) и other.item (i) имеют тип G, а значит, к ним применимы все операции NUMERIC, включая, инфиксный "+".
Следующие родовые порождения корректны, если полагать, что все классы, представленные как фактические родовые параметры, являются потомками NUMERIC:
Класс EMPLOYEE не порожден от NUMERIC, так что попытка использовать VECTOR [EMPLOYEE] приведет к ошибке времени компиляции.
Абстрактный характер NUMERIC не вызывает никаких проблем. Фактический параметр при порождении может быть как эффективным (примеры выше), так и отложенным (VECTOR [NUMERIC_COMPARABLE]), если он порожден от NUMERIC.
Аналогично описываются класс словаря и класс, поддерживающий сортировку:
Игра в рекурсию
Вот некий трюк с нашим примером: спросим себя, возможен ли вектор векторов? Допустим ли тип VECTOR [VECTOR [INTEGER]]?
Ответ следует из предыдущих правил: только если фактический родовой параметр совместим с NUMERIC. Сделать это просто - породить класс VECTOR от класса NUMERIC (см. упражнение 16.2):
Векторы, подобные этому, можно и впрямь считать "числовыми". Операции сложение и умножение дают структуру кольца, в котором роль нуля (zero) играет вектор из G– нулей, и роль единицы (unity) - вектор из G– единиц. Операция сложения в этом кольце - это, строго говоря, векторный вариант infix "+", речь о котором шла выше.
Можно пойти дальше и использовать VECTOR [VECTOR [VECTOR [INTEGER]]] и так далее - приятное рекурсивное приложение ограниченной универсальности.
И снова неограниченная универсальность
Конечно же, не все случаи универсальности ограничены. Форма - STACK [G] или ARRAY [G]– по-прежнему существует и называется неограниченной универсальностью. Пример DICTIONARY [G, H -> HASHABLE] показывает, что класс одновременно может иметь как ограниченные, так и неограниченные родовые параметры.