Бертран Мейер - Основы объектно-ориентированного программирования
p1 := new_point (u, v)
Точку можно задать: декартовыми координатами x и y; или полярными координатами r и q (расстоянием от начала координат и углом, отсчитываемым от горизонтальной оси). Но если перегрузить функцию new_point, то возникнет затруднение, связанное с тем, что оба варианта имеют одинаковую сигнатуру:
new_point (p, q: REAL): POINT
Этот пример, да и многие подобные ему, показывает, что сигнатура типов может не устранять неоднозначность перегружаемых вариантов. Но ничего лучшего не было предложено.
К сожалению, в относительно недавно появившемся языке Java используется описанная выше форма синтаксической перегрузки, в частности, для обеспечения альтернативных способов создания объектов.Семантическая перегрузка (предварительное представление)
Описанную форму перегрузки подпрограмм можно назвать синтаксической перегрузкой. В ОО-подходе будет предложена намного более интересная методика, динамическое связывание, отвечающая целям Независимости Представлений. Динамическое связывание можно назвать семантической перегрузкой. При использовании этой методики и соответствующим образом подобранном синтаксисе можно записать некоторый эквивалент has (t, x) как запрос на выполнение.
Смысл такого запроса примерно таков:
Дорогой Компьютер (Hardware-Software Machine):
Разберитесь, пожалуйста, что такое t; я знаю, что это должна быть таблица, но не знаю, какую реализацию этой таблицы выбрал ее создатель - и, откровенно говоря, лучше, если я останусь в неведении об этом. Как-никак, я занимаюсь не организацией ведения таблиц, а банковскими инвестициями [или компиляцией, или автоматизированным проектированием и т.д.]. Начальник над таблицами здесь кто-то другой. Так что разберитесь в этом сами и, когда получите ответ, поищите подходящий алгоритм для 'has', соответствующий этому конкретному виду таблицы. Затем используйте найденный алгоритм, чтобы установить, содержится ли 'x' в 't', и сообщите мне результат. Я с нетерпением ожидаю вашего ответа.
С сожалением сообщаю вам что, кроме информации о том, что 't' это некоторого рода таблица, а 'x' это ее возможный элемент, вы не получите от меня больше никакой помощи.
Примите мои дружеские пожелания,
Искренне Ваш разработчик приложений.
В отличие от синтаксической перегрузки, такая семантическая перегрузка является прямым ответом на требование Независимости Представлений. Все еще остается подозрение о нарушении принципа честности (non-deception), и ответом будет использование утверждений (assertions), задающих общую семантику подпрограммы, имеющей много различных вариантов (например, общие свойства, характеризующие has при всевозможных реализациях таблицы).
Поскольку для надлежащей работы механизма семантической перегрузки требуется использование всего ОО-аппарата, в частности - наследования, то понятно, что синтаксическая перегрузка является лишь полумерой. В ОО-языке наличие синтаксической перегрузки наряду с динамическим связыванием может лишь приводить к путанице, как это происходит в языках C++ и Java, которые позволяют классу использовать несколько процедур с одним и тем же именем, возлагая разрешение неоднозначности вызовов на компилятор и человека, читающего текст программы.
Универсальность (genericity)
Универсальность - это механизм определения параметризованных шаблонов модулей (module patterns), параметры которых представляют собой типы. Это средство является прямым ответом на требование Изменчивости Типов. Оно устраняет необходимость использования многих модулей, таких как:
INTEGER_TABLE_HANDLING
ELECTRON_TABLE_HANDLING
ACCOUNT_TABLE_HANDLING
Вместо этого разрешается написать единственный шаблон модуля в виде:
TABLE_HANDLING [G]
Имя G, представляющее произвольный тип, и называется формальным родовым параметром (formal generic parameter). (Позже мы можем встретиться с необходимостью иметь два или более родовых параметров, но сейчас ограничимся одним.)
Такой параметризованный шаблон называется универсальным модулем (generic module), хотя это еще не настоящий модуль, а лишь общая схема - шаблон многих возможных модулей. Для получения фактического модуля из шаблона, следует задать некоторый тип, называемый фактическим родовым параметром. Модули, получаемые из шаблона заменой формального параметра G на фактический, записываются, например, в виде:
TABLE_HANDLING [INTEGER]
TABLE_HANDLING [ELECTRON]
TABLE_HANDLING [ACCOUNT]
Типы INTEGER, ELECTRON и ACCOUNT использованы, соответственно, в качестве фактических родовых параметров. Такой процесс получения фактического модуля из универсального модуля (шаблона модуля) называется родовым порождением (generic derivation), а сам модуль будет называться "универсально порожденным" (generically derived.).
Два небольших замечания относительно терминологии. Во-первых, родовое порождение иногда называют родовой конкретизацией (generic instantiation), а порожденный модуль называют тогда родовым экземпляром (generic instance) Эта терминология может привести к недоразумениям в ОО-контексте, поскольку термин "экземпляр" применяется к объектам, созданные во время выполнения из соответствующих типов (или классов). Так что мы будем придерживаться термина "порождение".
Другим возможным источником недоразумений является слово "параметр". Подпрограмма может иметь формальные аргументы, представляющие значения, которые клиенты подпрограммы будут задавать при каждом обращении к ней. В литературе обычно используется термин "параметр" (формальный, фактический) как синоним аргумента (формального, фактического). Применение любого из этих терминов не является ошибкой, но мы, как правило, будем использовать термин "аргумент" для подпрограмм, а "параметр" для универсальных модулей.
Внутренне, описание унифицированного модуля TABLE_HANDLING будет напоминать приведенное выше описание INTEGER_TABLE_HANDLING, за исключением того, что для ссылки на тип элементов таблицы используется G вместо INTEGER. Например:
package TABLE_HANDLING [G] feature
type BINARY_TREE is
record
info: G
left, right: BINARY_TREE
end
has (t: BINARY_TREE; x: G): BOOLEAN
-- Содержится ли x в t?
do ... end
put (t: BINARY_TREE; x: G) is
-- Включить x в t.
do ... end
(и т.д.)
end -- пакета TABLE_HANDLING
В этом подходе некоторое замешательство вызывает то обстоятельство, что тип, объявленный BINARY_TREE, хотелось бы сделать универсальным и объявить его как BINARY_TREE [G]. Нет очевидного способа достижения этой возможности при "пакетном" подходе. Однако объектная технология объединит понятия модуля и типа, так что проблема будет решена автоматически. Мы убедимся в этом, когда узнаем, как интегрировать универсальность (genericity) в ОО-мир.
Интересно сопоставить определение универсальности с приведенным ранее определением перегрузки:
Роль универсальности
Универсальность является средством, предназначенным для поставщиков. Она позволяет писать один и тот же текст, используя одну и ту же реализацию некоторого понятия, применяемую к различным видам объектов.
Как же универсальность способствует реализации целей этой лекции? В отличие от синтаксической перегрузки, универсальность дает реальный вклад в решение наших проблем, поскольку, как было отмечено выше, она обеспечивает выполнение одного из основных требований, Изменчивости Типов. И при изложении объектной технологии в лекциях 7-18 этого курса значительное внимание будет уделено универсальности.
Основные методы модульности: оценка
Мы получили два основных результата. Одним из них является идея создания единого синтаксического "жилища", такого как пакетная конструкция (package construct), для множества подпрограмм, все из которых работают с однородными объектами. Вторым результатом является универсальность, приводящая к более гибкой форме модуля.
Все это, однако, охватывает лишь две проблемы повторного использования, Группирование Подпрограмм и Изменчивость Типов, и оказывает некоторое содействие в решении оставшихся трех проблем - Изменчивости Реализаций, Независимости Представлений и Факторизации Общего Поведения. Универсальность, в частности, недостаточна для решения проблемы Факторизации, поскольку определяет лишь два уровня. У нас появляется универсальный модуль, параметризованный и, следовательно, открытый для изменений, но непосредственно не применимый. На другом уровне у нас есть отдельные родовые порождения, пригодные для непосредственного применения, но закрытые для дальнейших изменений. Это не позволяет уловить тонкие различия, которые могут существовать между конкурирующими представлениями заданной общей идеи.