В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 72
Текст из файла (страница 72)
определяющий, например, тип "запись_об_узле" (пренебрегая защитой!), то
придется вспомнить о порядке раздельной трансляции.
Замечание. Фактически мы проявили "точку роста" наследуемости, пока,
насколько известно, не использованную авторами ЯП. Аналогично тому, как
старые операции воспринимают обогащенные структуры, так и "старые" структуры
могли бы воспринимать свои обогащенные компоненты, не требуя переименований
и перетрансляций. Точнее говоря, такие "транзитивные обогащения" должны быть
допустимыми и без перетрансляции, но последняя, возможно, окажется полезной
для оптимизации расхода ресурсов.
И вообще полезно понимать, что ожидаемые преимущества от нового стиля
программирования даются не бесплатно. Мы и раньше обращали внимание на тот
факт, что решение критичных проблем требует как соответствующих
выразительных средств, так и методики их применения для решения выделенной
проблемы. В частности, чтобы иметь возможность развивать программные услуги
с учетом идеала наследуемости, нужно заранее позаботиться о строении
предназначенных для такого развития услуг. В этом и проявляется определенная
методика программирования.
Упражнение (повышенной трудности). Перепишите модуль УправлениеСетями
так, чтобы было возможно обогащать узлы или записи об узле, а затем напишите
соответствующие обогащения.
18.6.2. Аспект операций
Мы рассмотрели такое обогащения сетей, при котором новый атрибут не
влияет на осмысленность старых операций. Точнее говоря, мы были вынуждены
заменить определения операций "вставить" и "присвоить", однако при этом с
успехом пользовались в новом контексте и старыми одноименными операциями.
Но нетрудно указать на такие исходные типы и такие их обогащения, когда
исходные операции совершенно теряют смысл. Классический пример - тип
"окружность" с операциями вывода на экран или принтер, и обогащение "дуга
окружности" с новыми атрибутами "начальный_угол" и "величина дуги". Ясно,
что печать дуги на принтере нельзя (или трудно, неестественно) представить
через печать полной окружности - это противоречит самой сути новых атрибутов
(предназначенных как раз для вырезания лишь части окружности).
Назовем подобные обогащения ограничивающими. Ясно, что для
ограничивающих обогащений необходимо иметь механизм полной замены исходных
операций над объектами старых типов.
В Обероне специального аппарата для подобной цели нет. Однако можно
воспользоваться переменными процедурного типа. Тем более, что такие
переменные могут быть и полями объектов комбинированного типа.
Например, исходный модуль УправлениеСетями может иметь вид
DEFINITION УправлениеСетями;
IMPORT П: ПараметрыСети;
TYPE
Узел = SHORTINT;
ПереченьСвязей = ARRAY П.МаксСвязей OF Узел;
Связи = RECORD
Число : SHORTINT;
Узлы : ПереченьСвязей;
END;
Сети = RECORD END;
VAR (* переменные процедурных типов *)
УзелЕсть : PROCEDURE (Узел, VAR Сети) : BOOLEAN;
ВсеСвязи : PROCEDURE (Узел, VAR Сети, VAR Связи);
Вставить : PROCEDURE (Узел, VAR Сети);
Присвоить : PROCEDURE (VAR Сети, VAR Сети);
Связать : PROCEDURE (Узел, Узел, VAR Сети);
Удалить : PROCEDURE (Узел, VAR Сети);
END УправлениеСетями;
MODULE УправлениеСетями;
IMPORT П: ПараметрыСети;
TYPE
Узел = SHORTINT;
ПереченьСвязей = ARRAY П.МаксСвязей OF Узел;
Связи = RECORD
Число : SHORTINT;
Узлы : ПереченьСвязей;
END;
ЗаписьОбУзле = RECORD
Включен : BOOLEAN;
Связан : Связи;
END;
Сети = RECORD C: ARRAY П.МаксУзлов OF ЗаписьОбУзле END;
VAR (* переменные процедурных типов *)
УзелЕсть : PROCEDURE (Узел, VAR Сети) : BOOLEAN;
ВсеСвязи : PROCEDURE (Узел, VAR Сети, VAR Связи);
Вставить : PROCEDURE (Узел, VAR Сети);
Присвоить : PROCEDURE (VAR Сети, VAR Сети);
Связать : PROCEDURE (Узел, Узел, VAR Сети);
Удалить : PROCEDURE (Узел, VAR Сети);
(* ниже следуют константы соответствующих процедурных типов *)
PROCEDURE УзелЕсть1 (X: Узел; VAR ВСети : Сети) : BOOLEAN;
BEGIN
RETURN ВСети.С[X].Включен;
END УзелЕсть1;
PROCEDURE ВсеСвязи1 (X : Узел; VAR ВСети : Сети; VAR R : Связи);
BEGIN
R := ВСети.С[X].Связан;
END ВсеСвязи1;
PROCEDURE Вставить1 (X : Узел; VAR ВСеть : Сети);
BEGIN
ВСеть.С[X].Включен := TRUE;
ВСеть.С[X].Связан.Число := 0;
END Вставить1;
PROCEDURE Присвоить1 (VAR Сеть1, Сеть2 : Сети);
BEGIN
Сеть2.С := Сеть1.С;
END Присвоить1;
PROCEDURE Есть_связь(АУзел, ВУзел : Узел, VAR ВСети : Сети): BOOLEAN;
VAR i : 1..П.МаксСвязей;
z : Связи;
BEGIN
z := ВСети.С[АУзел].Связан;
i := 0;
REPEAT (* цикла FOR в Обероне нет *)
IF z.Узлы(i) = BУзел THEN
RETURN TRUE ;
END;
i := i + 1;
UNTIL i < z.Число
RETURN FALSE ;
END Есть_связь ;
PROCEDURE Установить_связь(Откуда, Куда : Узел; VAR ВСети : Сети);
VAR z: Связи;
BEGIN
z := ВСети.С[АУзел].Связан;
z.Число := z.Число+1 ;
z.Узлы(z.Число) := Куда ;
END Установить_связь ;
PROCEDURE Связать1 (АУзел, ВУзел : Узел; VAR ВСети : Сети);
BEGIN
IF ~ Есть_связь(АУзел, ВУзел, ВСети) THEN (* "~" - отрицание *)
Установить_связь(АУзел, ВУзел, ВСети) ;
IF АУзел # ВУзел THEN (* "#" в Обероне - знак неравенства *)
Установить_связь(ВУзел, АУзел) ;
END;
END;
END Связать1 ;
PROCEDURE Переписать (ВУзле : Узел;
После : SHORTINT; VAR ВСети : Сети);
VAR j : SHORTINT;
BEGIN
j := После;
WHILE J > ВСети.C[ВУзле].Связан.Число-1 DO
ВСети.C[ВУзле].Связан.Узлы[j] := ВСети.C[ВУзле].Связан.Узлы[j+1];
j := j+1;
END
END Переписать;
PROCEDURE Чистить (Связь, ВУзле : Узел; VAR ВСети : Сети);
VAR i : SHORTINT;
BEGIN
i := 0;
REPEAT
IF ВСети.С[ВУзле].Связан.Узлы[i] = Связь THEN
Переписать (ВУзле, i, ВСети);
ВСети.С[ВУзле].Связан.Число := ВСети.С[ВУзле].Связан.Число-1;
EXIT;
END; i := i+1;
UNTIL i < ВСети.С[ВУзле].Связан.Число
END Чистить;
PROCEDURE Удалить1 (X : Узел; VAR ИзСети : Сети);
VAR i : SHORTINT;
BEGIN
ИзСети.С[X].Включен := FALSE; i := 0;
REPEAT (* цикла FOR в Обероне нет *)
Чистить (X, ИзСети.C[X].Связан.Узлы[i], ИзСети);
i := i+1;
UNTIL i < ИзСети.C[X].Связан.Число
END Удалить1;
BEGIN (* Инициализация - работает при загрузке модуля *)
УзелЕсть := УзелЕсть1;
ВсеСвязи := ВсеСвязи1;
Вставить := Вставить1;
Присвоить := Присвоить1;
Связать := Связать1;
Удалить := Удалить1;
END УправлениеСетями;
А модуль УправлениеСетямиСВесом принимает вид
DEFINITION УправлениеСетямиСВесом;
IMPORT У: УправлениеСетями, П: ПараметрыСети;
TYPE
Вес = SHORTINT;
Узел = У.Узел;
Сети = RECORD END;
VAR
УзелЕсть : PROCEDURE (Узел, VAR Сети) : BOOLEAN;
ВсеСвязи : PROCEDURE (Узел, VAR Сети, VAR Связи);
Вставить : PROCEDURE (Узел, VAR Сети, Вес);
Присвоить : PROCEDURE (VAR Сети, VAR Сети);
Связать : PROCEDURE (Узел, Узел, VAR Сети);
Удалить : PROCEDURE (Узел, VAR Сети);
ВесПути : PROCEDURE (Узел, Узел, VAR Сети): Вес;
END УправлениеСетямиСВесом;
MODULE УправлениеСетямиСВесом;
IMPORT У: УправлениеСетями, П: ПараметрыСети;
TYPE
Вес = SHORTINT;
Узел = У.Узел;
Сети = RECORD (У.Сети) В: ARRAY П.МаксУзлов OF Вес END;;
(* обогащенные сети *)
VAR
УзелЕсть : PROCEDURE (Узел, VAR Сети) : BOOLEAN;
ВсеСвязи : PROCEDURE (Узел, VAR Сети, VAR Связи);
Вставить : PROCEDURE (Узел, VAR Сети, Вес);
Присвоить : PROCEDURE (VAR Сети, VAR Сети);
Связать : PROCEDURE (Узел, Узел, VAR Сети);
Переписать: PROCEDURE (Узел, SHORTINT, VAR Сети);
Удалить : PROCEDURE (Узел, VAR Сети);
ВесПути : PROCEDURE (Узел, Узел, VAR Сети): Вес;
(* ниже следуют процедурные константы, заменяющие исходные *)
PROCEDURE Вставить1 (X : Узел; VAR ВСеть : Сети; P: Вес);
BEGIN
У.Вставить (X, ВСеть); (* аргумент может быть обогащенным! *)
ВСеть.В[X] := P;
END Вставить1;
PROCEDURE Присвоить1 (VAR Сеть1, Сеть2 : Сети);
BEGIN
У.Присвоить (Сеть1, Сеть2);
Сеть2.B := Сеть1.B;
END Присвоить1;
PROCEDURE ВесПути1 (X,Y: Узел; VAR ВСети : Сети): Вес;
...
BEGIN
...
RETURN Вес;
END ВесПути1;
BEGIN (* Инициализация - работает при загрузке модуля *)
УзелЕсть := У.УзелЕсть; (* старая *)
ВсеСвязи := У.ВсеСвязи; (* старая *)
Вставить := Вставить1; (* новая *)
Присвоить := Присвоить1; (* новая *)
Связать := У.Связать; (* старая *)
Удалить := У.Удалить; (* старая *)
ВесПути := ВесПути1; (* новая *)
END УправлениеСетямиСВесом;
Таким образом, появляется возможность при обогащении устанавливать
значения нужных процедур и в старом, и в новом модуле. Однако имеется два
неудобства - во-первых, в старом модуле невозможно предусмотреть тип
"процедур будущего". Например, в нашей новой процедуре "Вставить" появился
новый параметр и в результате типы старой и новой процедур несовместимы. Во-
вторых, нет явной связи обогащенного типа именно с новыми операциями - можно
по ошибке применить и старые операции, причем диагностики никакой не будет
(ведь возможно, этого и хотелось!).
В принципе, как уже сказано, Оберон позволяет связать нужные операции и
с каждой сетью индивидуально. Для этого можно использовать поля процедурного
типа в объекте типа "сети_с_процедурами", который можно объявить следующим
образом.
TYPE
...
Сети = RECORD (У.Сети)
В: ARRAY П.МаксУзлов OF Вес
УзелЕсть : PROCEDURE (Узел, VAR Сети) : BOOLEAN;
ВсеСвязи : PROCEDURE (Узел, VAR Сети, VAR Связи);
Вставить : PROCEDURE (Узел, VAR Сети, Вес);
Присвоить : PROCEDURE (VAR Сети, VAR Сети);
Связать : PROCEDURE (Узел, Узел, VAR Сети);
Переписать: PROCEDURE (Узел, SHORTINT, VAR Сети);
Удалить : PROCEDURE (Узел, VAR Сети);
ВесПути : PROCEDURE (Узел, Узел, VAR Сети): Вес;
END;
Тем самым в Обероне обеспечен и необходимый минимум средств для
"настоящего" объектно-ориентированного программирования. Так что в
"чемоданчике" и на этот случай многое предусмотрено. Конечно, аналогичные
приемы применимы и к Модуле-2, но там придется моделировать и встроенный в
Оберон механизм обогащения типов.
Упражнение. Найдите способ программировать в объектно-ориентированном
стиле средствами Модулы-2.
Подсказка. Процедурные типы в Модуле-2 имеются.
Правда, отсутствие соответствующего управления видимостью не позволяет
средствами Оберона легко и естественно выполнять привязку конкретных
процедур к конкретным объектам. Например, процедура с именем "Вставить" в
общем случае никак не связана с полем "Вставить" ни в объявлении типа
"Сети", ни с полем "Вставить" в объекте, например, "Сеть1". Приходится
воплощать необходимые связи явными присваиваниями (например, процедуры
Вставить полю Сеть1.Вставить). Нужные средства (в том числе и управления
видимостью) предоставляют более мощные ЯП с развитой наследуемостью -
например, Турбо Паскаль 5.5, примеры программ на котором мы рассмотрим в
следующем разделе.
18.7. Концепция наследования в ЯП (краткий обзор)
Накоплено достаточно материала, чтобы поговорить об общих свойствах
наследуемости в ЯП. Сделаем это в форме изложения элементов частично
формализованной концепции (теории) наследования и краткого обзора воплощения
этой концепции в современных ЯП.
18.7.1. Основные понятия и неформальные аксиомы наследования.
1. Основные понятия:
отношение наследования (короче "наследование") между родителем и
наследником; например, отношение между типами "Сети" и "СетиСВесом".
атрибуты наследования (атрибуты); например, процедуры Удалить и др.
разность атрибутов наследника и родителя (накопление); например,
процедуры ВесПути и Вставить.
типы объектов и экземпляры (объекты) определенных типов; например,
"Сети" и "Сеть1".
В терминах этих понятий сформулируем аксиомы наследования, проявляя
связи (и тем самым определяя) перечисленные понятия. Главная цель пункта -
уточнить понятия и подготовиться к изложению математической концепции
(модели) наследования.
2. Отношение наследования определяется для типов, а не экземпляров
(объектов).
Обратите внимание, в живой природе - наоборот, индивиды-наследники
наследуют свойства индивидов-родителей! Стоит задуматься над причиной такого
различия [#26], а может быть и усмотреть интересные перспективы.
3. Наследник обладает всеми атрибутами (наследования) родителя.
Обратное неверно.
4. Право участвоать в операциях определенного класса - это атрибут