В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 71
Текст из файла (страница 71)
Абстракция от реализации.
Конечно, всегда можно воспользоваться методом, который мы применили,
переходя от одной сети ко многим - скорректировать сам пакет таким образом,
чтобы удовлетворить новым потребностям.
Вопрос. Каковы недостатки предлагаемого пути?
Подсказка. Где гарантия, что старые программы - клиенты нашего пакета
останутся работоспособными после его модификации? Кстати, кто разрешил
переделывать пакет? Ведь в приличном обществе его охраняет авторское право
(к тому же тел исходные тексты могут оказаться недоступными - в соответствии
с условиями контракта).
Вопрос. Каковы преимущества предлагаемого пути?
Подсказка. Не нужно осваивать новые средства программирования!
Итак, будем считать, что путь переделки комплекса программных услуг в
связи с потребностью в новых услугах, казавшийся до сих пор привычным и
самым естественным - путь "революции", чреватый поломкой и того, что раньше
неплохо работало - путь неприемлемый. Постараемся подняться на новый уровень
требований к средствам программирования, а именно потребуем "абстракции от
реализации" - чтобы можно было развивать услуги, совершенно ничего не зная
об их реализации! Причем развивать по меньшей мере так, как нам
потребовалось - чтобы можно было ввести сети с весомыми узлами.
Аспект данных проблемы атрибутов.
Может показаться, что нечто подобное "мы уже проходили", когда шла речь
о производных типах и наследовании в Аде. С этой точки зрения было бы
естественным ввести "производный" тип сети_с_весом, наследующий все операции
для типа "сети" из пакета управление_сетями, и к тому же обладающий
дополнительным атрибутом "вес_узлов".
Но в том то и дело, что в Аде невозможно естественно представить такой
дополнительный атрибут!
Вопрос: почему?
Подсказка: производные типы Ады по построению богаче родительских
только за счет операций, а не за счет развития структуры данных.
Следовательно, атрибут "вес_узлов" придется вводить только за счет
дополнительных операций, работающих с новыи атрибутом. Это было бы вполне
приемлемым, если бы для работы таких операций в структуре сети было заранее
предусмотрено подходящее поле для хранения значения веса узла. Но ни о чем
подобном мы не думали (и не могли думать), создавая тип "сети". Более того,
даже если бы, предвидя потребность в развитии, мы предусмотрели "запасное"
поле в таком типе, то строгая типизация заставила бы нас определить сразу же
и тип его поля. Так что построить тип "сети_с_весом", не меняя пакет
"управление_сетями", в Аде невозможно.
Вопрос: не помогут ли ссылочные типы?
Ответ очевиден. Предоставляем его читателю.
Вопрос: не помогут ли родовые параметры?
Подсказка: формально они спасают положение - можно ввести запасной
родовой параметр-тип специально для наращивания типа "сети" при развитии
нашего пакета.
Читателю предлагается самостоятельно оценить уровень изящества такого
решения с учетом того, что запасные параметры придется вводить для любых
типов в пакетах, претендующих на развитие рассматриваемого характера,
пользоваться пакетом станет возможным только после конкретизации (возможно,
с типами-заглушками вместо запасных типов, оказавшихся пока не развитыми),
причем желательно требовать, чтобы транслятор не расходовал память для
заглушек.
Конечно, всегда остается упомянутая выше (и отвергнутая) возможность
напмсать новый пакет "управление_сетями_с_весом", воспользовавшись исходным
пакетом как образцом. Но тогда уж производные типы (м вообще средства
развития) окажутся не при чем. Кстати, родовые сегменты частично
автоматизируют именно такое переписывание, однако применимы и тогда, когда
их исходные тексты недоступны. [Можно, конечно, ввести новый тип с полем
"вес", не используя явно тип "сети". Но тогда для него придется определять и
все операции, которые могли бы наследоваться. Другими словами, такое решение
содержательно почти эквивалентно созданию нового пакета.]
Назовем описанную проблему развития услуг проблемой дополнительных
атрибутов. Подчеркнем, что речь идет об атрибутах конкретных значений
(экземпляров) типа, а не атрибутах типа в целом (к которым относятся, в
частности, его базовые операции). Другими словами, конкретным весом обладвет
конкретный узел в конкретной сети, а не весь тип "сети". А вот, например,
операция "связать" - атрибут всего типа "сети" - применима к любой паре
узлов в произвольной сети.
Аспект операций проблемы атрибутов.
Обратим внимание еще на один аспект проблемы дополнительных атрибутов.
Мы пытались применить для ее решения средства наследования - производные
типы Ады - именно потому, что желательно, чтобы старые операции над сетями
были применимы и к обогащенным сетям, т.е. сетям с весом. Другими словами,
старые операции должны работать со значениями, структура которых неизвестна
не только при создании определений этих операций, но и при их трансляции.
Вопрос. При чем здесь трансляция?
Подсказка. Мы должны учитывать возможное отсутствие даже исходных
текстов пакета управление_сетями.
Более того, должны работать и все программы пользователя, написанные и
оттранслированные до момента, когда он задумал работать с обогащенными
сетями (с весом). Конечно, в этих программах не могут непосредственно
использоваться операции с новыми именами - о них просто ничего не было
известно при создании программ. Вместе с тем некоторые используемые в старых
программах операции могут существенно зависеть от структуры и иных
характеристик конкретных значений новых типов (например, для операции,
показывающей сеть на экране, небезразличны количество и типы ее атрибутов).
Поэтому в общем случае решение проблемы дополнительных атрибутов должно
предусматривать возможность подставлять в старые программы вместо старых тел
операций их новые тела, учитывающие характеристики значений новых типов.
Указанный аспект проблемы назовем аспектом операций (в отличие от ранее
рассмотренного аспекта данных). Подчеркнем, что аспект операций не сводится
к аспекту данных и наоборот, приходится предусматривать специфические
средства развития операций.
Мы пришли к очередной неформальной теореме: в Аде нет средств для
адекватного решения проблемы дополнительных атрибутов.
18.6. Развитая наследуемость
Итак, чтобы решить проблему дополнительных атрибутов (и тем самым
воплотить близкую к идеалу гармонию между защитой работоспособности старых
программ и оптимизацией усилий по их развитию) само понятие наследуемости
нуждается в развитии по сравнению с наследуемостью в Аде.
Ключевые моменты такого развития: для аспекта данных - обогащение типа
(возможность вводить дополнительные поля при объявлении производного типа),
для аспекта операций - виртуальные операции (возможность вводить операции,
заменяющие старые операции при действиях с обогащенными значениями - даже в
старых программах). Последнее может показаться фантастичным, и тем не менее
это всего лишь (частично) динамическая вариация на тему перекрытия операций.
18.6.1. Аспект данных
Покажем решение задачи обогащения сетей средствами Оберона (т.е.
минимальными средствами). Можно надеяться, что после этого будут легче
восприниматься средства более мощных ЯП.
Чтобы избежать слишком подробных объяснений, сначала представим
решение, потом прокомментируем.
DEFINITION УправлениеСетямиСВесом;
IMPORT У: УправлениеСетями, П: ПараметрыСети;
CONST
Удалить = У.Удалить;
Связать = У.Связать;
УзелЕсть = У.УзелЕсть;
ВсеСвязи = У.ВсеСвязи;
(* объявление процедур-констант использовано для переименования *)
(* Вопрос. Зачем оно нужно? *)
TYPE
Вес = SHORTINT;
Узел = У.Узел;
Сети = RECORD END;
PROCEDURE Вставить (X: Узел; VAR ВСети : Сети; P: Вес);
PROCEDURE Присвоить ( VAR Сеть1, Сеть2 : Сети);
PROCEDURE ВесПути (X,Y: Узел; VAR ВСети : Сети): Вес;
(* эти процедуры существенно зависят от обогащения *)
END УправлениеСетямиСВесом;
MODULE УправлениеСетямиСВесом;
IMPORT У: УправлениеСетями, П: ПараметрыСети;
CONST
Удалить = У.Удалить;
Связать = У.Связать;
УзелЕсть = У.УзелЕсть;
ВсеСвязи = У.ВсеСвязи;
TYPE
Вес = SHORTINT;
Узел = У.Узел;
Сети = RECORD (У.Сети) В: ARRAY П.МаксУзлов OF Вес END;;
(* обогащенные сети *)
PROCEDURE Вставить (X : Узел; VAR ВСеть : Сети; P: Вес);
BEGIN
У.Вставить (X, ВСеть); (* аргумент может быть обогащенным! *)
ВСеть.В[X] := P;
END Вставить;
PROCEDURE Присвоить (VAR Сеть1, Сеть2 : Сети);
BEGIN
У.Присвоить (Сеть1, Сеть2);
Сеть2.B := Сеть1.B;
END Присвоить;
(* Вопрос. Зачем оба параметра с режимом VAR? *)
(* Подсказка. Стоит ли копировать сети? *)
(* Вопрос. Зачем используются процедуры из пакета У? *)
(* Подсказка. Без них - никак; вспомните о видимости *)
PROCEDURE ВесПути (X,Y: Узел; VAR ВСети : Сети): Вес;
...
BEGIN
...
RETURN Вес;
END ВесПути;
END УправлениеСетямиСВесом;
Упражнение. Запрограммируйте функцию ВесПути, используя процедуру
ВсеСвязи.
Итак, мы написали модуль, позволяющий работать с обогащенными сетями и
при этом использующий (и сохраняющий) все средства работы со старыми сетями.
Самое для нас существенное - ничего не потребовалось знать об их реализации
- мы пользовались только видимыми пользователю атрибутами старых сетей!
Заново пришлось программировать только операции, существенно использующие
новые атрибуты, но и при этом было удобно пользоваться операциями над
старыми сетями - надежно и естественно - без нарушения авторского права.
Конечно, можно еще более сократить дополнительные усилия и не вводить
переименований старых имен. Однако пользоваться обогащенными сетями было бы
менее удобно (за счет чего?). Кстати, переименования и не потребовались бы,
если обогащенный тип "сети" мы ввели бы прямо в модуль УправлениеСетями
(полезное упражнение). Однако хотелось показать, как задача полностью
решается чисто модульными средствами Оберона без всякой перетрансляции
исходного модуля.
Сделаем еще ряд технических пояснений, хотя можно надеяться, что пример
уже полностью понятен. Обратите внимание, что в реализации модуля заново
повторяется все, что указано в спецификации (в отличие от Ады и Модулы-2).
Этого требует принцип экспортного окна. Существенно эксплуатируется
возможность обращаться к старым процедурам с аргументами обогащенного типа
(где, например?) - при этом старые процедуры используют и модифицируют
только старые поля - хотя, с другой стороны, прямые присваивания старых
объектов новым в Обероне запрещены!
Вопрос. Зачем такой запрет?
Подсказка. Вспомните о надежности.
Может показаться, что было бы естественнее обогащать не сети, а узлы
или записи_об_узле. Однако в каждом из этих случаев возникают технические
препятствия, заслоняющие суть дела и потому мешающие привести такое
обогащение в качестве простого примера. Во-первых, тип "узел" не является
комбинированным, а только для комбинированных типов в Обероне допустимо
обогащение. Во-вторых, тип запись_об_узле скрыт в теле модуля и поэтому
нельзя его обогащать извне модуля.
Наконец, обогащение типа "узел" формально никак не скажется на
построенных на его основе составных типах - они такого обогащения "не
заметят" (это справедливо, конечно, и для типа "запись_об_узле").
Вопрос. Как сделать, чтобы "заметили"?
Подсказка. Посмотрите на раздел констант модуля УправлениеСетямиСВесом.
Вопрос. Можно ли при этом обойтись без перетрансляции модуля
УправлениеСетями?
Подсказка. Ответ очевиден. И даже если предварительно выделить модуль,