Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Языки программирования. Концепции и принципы
.pdf
Наследуемость (к идеалу развития и защиты в ЯП)
Вопрос. Можно ли при этом обойтись без перетрансляции модуля Управление_
Сетями?
Подсказка. Ответ очевиден. И даже если предварительно выделить модуль, опре
деляющий, например, тип «запись_об_узле» (пренебрегая защитой!), то придется
вспомнить о порядке раздельной трансляции.
Замечание. Фактически мы проявили «точку роста» наследуемости, пока, на
сколько известно, не использованную авторами ЯП. Аналогично тому, как старые
операции воспринимают обогащенные структуры, так и «старые» структуры могли
бы воспринимать свои обогащенные компоненты, не требуя переименований и
перетрансляций. Точнее говоря, такие «транзитивные обогащения» должны быть
допустимыми и без перетрансляции, но последняя, возможно, окажется полезной
для оптимизации расхода ресурсов.
401
И вообще полезно понимать, что ожидаемые преимущества от нового стиля
программирования даются не бесплатно. Мы и раньше обращали внимание на тот
факт, что решение критичных проблем требует как соответствующих выразитель
ных средств, так и методики их применения для решения выделенной проблемы.
В частности, чтобы иметь возможность развивать программные услуги с учетом
идеала наследуемости, нужно заранее позаботиться о строении предназначенных
для такого развития услуг. В этом и проявляется определенная методика про
граммирования.
Упражнение (повышенной трудности). Перепишите модуль УправлениеСетями
так, чтобы было возможно обогащать узлы или записи об узле, а затем напишите
соответствующие обогащения.
6.9. Аспект операций
Мы рассмотрели такое обогащение сетей, при котором новый атрибут не влияет
на осмысленность старых операций. Точнее говоря, мы были вынуждены заме
нить определения операций «вставить» и «присвоить», однако при этом с успехом
пользовались в новом контексте и старыми одноименными операциями.
Но нетрудно указать на такие исходные типы и такие их обогащения, когда
исходные операции совершенно теряют смысл. Классический пример – тип «ок
ружность» с операциями вывода на экран или принтер, и обогащение «дуга ок
ружности» с новыми атрибутами «начальный_угол» и «величина дуги». Ясно, что
печать дуги на принтере нельзя (или трудно, неестественно) представить через
печать полной окружности – это противоречит самой сути новых атрибутов
(предназначенных как раз для вырезания лишь части окружности).
Назовем подобные обогащения ограничивающими. Ясно, что для ограничива
ющих обогащений необходимо иметь механизм полной замены исходных опера
ций над объектами старых типов.
В Обероне специального аппарата для подобной цели нет. Однако можно вос
пользоваться переменными процедурного типа. Тем более что такие переменные
могут быть и полями объектов комбинированного типа.

402
Перспективы языков программирования
Например, исходный модуль УправлениеСетями может иметь вид:
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 С: 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;
ВСеть.С[Х].Связан.Число:= 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)= ВУзел 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 > ВСети.С[ВУзле].Связан.Число-l DO
ВСети.С [ВУзле].Связан.Узлы[j]:= ВСети.С[ВУзле].Связан.Узлы [j+1];
j:= j+i;
403

404
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
ИзСети.С[Х].Включен:=FALSE; i:=0;
REPEAT (* цикла FOR в Обероне нет *)
Чистить(X, ИзСети.С[Х].Связан.Узлы[i], ИзСети);
i := i+1;
UNTIL i < ИзСети.С[Х].Связан.Число
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(Х : Узел; VAR ВСеть : Сети; Р: Вес);
BEGIN
У.Вставить(Х, ВСеть);
(* аргумент может быть обогащенным! *)
ВСеть.В[Х] := Р;
END Вставить1;
PROCEDURE Присвоить1(VAR Сеть1, Сеть2 : Сети);
BEGIN
У.Присвоить(Сеть1, Сеть2);
Сеть2.В := Сеть1.В;
END Присвоить1;
PROCEDURE ВесПути 1(X,Y: Узел; VAR ВСети : Сети): Вес,
...
BEGIN
...
RETURN Вес;
END ВесПути1;
BEGIN (* Инициализация – работает при загрузке модуля *)
УзелЕсть := У.УзелЕсть; (* старая *)
ВсеСвязи := У.ВсеСвязи; (* старая *)
Вставить := Вставить 1; (* новая *)
Присвоить := Присвоить 1; (* новая *)
Связать := У.Связать; (* старая *)
Удалить := У.Удалить; (* старая *)
ВесПути := ВесПути1; (* новая *)
END УправлениеСетямиСВесом;
405
Таким образом, появляется возможность при обогащении устанавливать зна
чения нужных процедур и в старом, и в новом модулях. Однако имеются два не

406
Перспективы языков программирования
удобства: вопервых, в старом модуле невозможно предусмотреть тип «процедур
будущего». Например, в нашей новой процедуре «Вставить» появился новый па
раметр, и в результате типы старой и новой процедур несовместимы. Вовторых,
нет явной связи обогащенного типа именно с новыми операциями – можно по
ошибке применить и старые операции, причем диагностики никакой не будет
(ведь, возможно, этого и хотелось!).
В принципе, как уже сказано, Оберон позволяет связать нужные операции и
с каждой сетью индивидуально. Для этого можно использовать поля процедурно
го типа в объекте типа «сети_с_процедурами», который можно объявить следую
щим образом:
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, примеры программ на котором мы рассмотрим в следующем
разделе.

Наследуемость (к идеалу развития и защиты в ЯП)
407
6.10. Концепция наследования в ЯП
(краткий обзор)
Накоплено достаточно материала, чтобы поговорить об общих свойствах насле
дуемости в ЯП. Сделаем это в форме изложения элементов частично формализо
ванной концепции (теории) наследования и краткого обзора воплощения этой
концепции в современных ЯП.
6.10.1. Основные понятия и неформальные
аксиомы наследования
Основные понятия:
• отношение наследования (короче – наследование) между родителем и на
следником; например отношение между типами «Сети» и «СетиСВесом»;
• атрибуты наследования (атрибуты); например процедуры Удалить и др.;
• разность атрибутов наследника и родителя (накопление); например проце
дуры ВесПути и Вставить;
• типы объектов и экземпляры (объекты) определенных типов; например
«Сети» и «Сеть1».
В этих терминах сформулируем аксиомы наследования, проявляя связи пере
численных понятий (и тем самым определяя последние). Главная цель пункта –
уточнить понятия и подготовиться к изложению математической концепции (мо
дели) наследования.
Отношение наследования определяется для типов, а не экземпляров (объек
тов). Обратите внимание, в живой природе – наоборот: индивидынаследники на
следуют свойства индивидовродителей! Стоит задуматься над причиной такого
различия, а может быть, и усмотреть интересные перспективы.
Наследник обладает всеми атрибутами родителя. Обратное неверно.
Право участвовать в операциях определенного класса – это атрибут наследо
вания. Следствие: наследник имеет право замещать родителя в любых таких опе
рациях.
Вопрос об определении указанного класса непрост. В Обероне присваивание
в него попадает лишь частично – обогащенным объектом можно замещать источ
ник присваивания, но не получатель.
Все экземпляры (объекты) одного типа обладают идентичными атрибутами
(но не их значениями!).
Следствие: индивидуальные свойства объектов (определяемые, в частности,
значениями атрибутов) не наследуются и по наследству не передаются.
Наличие наследников не влияет на атрибуты родителя.

408
Следствие: свойство «иметь наследников» не считается атрибутом (и, стало
быть, не наследуется).
Атрибуты могут быть абстрактными (виртуальными) и тем самым требовать
конкретизации (настройки на контекст перед использованием или в процессе ис
пользования), или конкретными, и тем самым такой настройки не требовать.
Пример абстрактного атрибута – поле «С» в типе «Сети». Его конкретизация –
поле с именем «С» в конкретном объекте типа «Сети», например Сеть1.С. Пример
конкретного атрибута типа «Сети» – процедура Удалить.
Результат конкретизации абстрактных атрибутов в общем случае определя
ется тем контекстом, где объекты создаются. Следствие: поскольку в этот кон
текст входят не только типы, но и конкретные объекты (экземпляры типов), то
конкретные значения абстрактных атрибутов могут зависеть от свойств конкрет
ных объектов контекста, а не только от их типов.
Настройку на контекст естественно выполнять при создании (инициализа
ции) объектов. Например, объекточередник при создании получает конкретное
значение атрибута, указывающего на стоящего впереди объекта.
Накопление в некоторых ЯП может состоять как из абстрактных, так и из
конкретных атрибутов (Смолток, Оберон, Турбо Паскаль 5.5), а в других – толь
ко из конкретных атрибутов (Ада).
Следствие: в ЯП второй категории значения атрибутов не могут учитывать
свойства (и даже сам факт существования) объектов таких типов, которые не су
ществовали в момент написания (трансляции) программы.
Итак, операцииатрибуты Ады могут служить примерами конкретных атрибу
тов. Они принадлежат только производному типу, а не индивидуальным объек
там этого типа в том смысле, что значения этих атрибутов (то есть конкретные
функции и процедуры) никак не зависят от экземпляров объектов производного
типа, не связаны с ними по происхождению и не устанавливаются при создании
объектов. Более того, из5за ограничений строгой типизации в Аде нельзя постро5
ить тип объектов, какие5либо производные которого могли бы содержать ссылки
(указатели) на экземпляры объектов производного типа.
Упражнение. Докажите неформальную теорему, сформулированную в последнем
предложении предыдущего абзаца.
Перспективы языков программирования
Примерами абстрактных атрибутов могут служить, во5первых, атрибутыполя
комбинированных типов (в любых ЯП, где есть наследование). Их естественно
считать абстрактными именно потому, что с типом связаны только имена полей,
а конкретные значения этих атрибутов (то есть конкретные поля конкретных эк
земпляров объектов – не путать со значениями, которые можно присваивать
этим полям!) возникают только при создании объектов. Другими словами, их
связь с объектами индивидуальна.
В тех ЯП, где атрибутыполя входят в накопление (Симула67, Оберон, Турбо
Паскаль 5.5 и др.), в объекты производных типов могут входить поля, содержащие
(или ссылающиеся на) объекты производных типов. Именно потому, что эти поля
формируются в контексте, где уже доступны обогащенные (производные) типы,

Наследуемость (к идеалу развития и защиты в ЯП)
строгая типизация не мешает их формированию, в отличие от ситуации, когда в
накоплении допустимы только конкретные атрибуты (Ада).
Во5вторых, примерами абстрактных атрибутов служат виртуальные операции
(операции Симулы67, правила (методы) Турбо Паскаля 5.5, Смолтока и др.).
Подробнее о них – в следующем разделе.
Итак, введенные понятия позволяют проявить весьма существенные различия
концепции наследования в современных ЯП. Например, в Модуле2 наследова
ние отсутствует совершенно (хотя можно усмотреть его зачатки в связи модулей
по импорту), в Аде – наследование только с конкретным накоплением, в Обероне –
возможно и абстрактное накопление, но нет виртуальных операций. Наконец,
в Турбо Паскале 5.5 нас ждет почти идеальное наследование. «Почти» – потому,
в частности, что имеются точки роста, – вспомните недавнее замечание об обога
щении составных типов.
С другой стороны, вдумчивого читателя еще со времени первого знакомства
с Обероном, возможно, мучит вопрос о правомерности (или неправомерности)
присваивания бедных объектов обогащенным и вообще о применимости старых
операций к новым объектам.
Можно, конечно, искать общий ответ, и мы предложим на этот счет некоторые
соображения. Однако не менее интересно и поучительно убедиться, что обозна
ченная проблема исчезает совершенно, если в центр внимания поместить не опе
рации, а объекты, что и сделано в объектноориентированном программировании.
Действительно, пусть сами объекты «решают», с кем им нужно взаимодейство
вать, а с кем – нет.
Прежде чем подробнее заняться объектноориентированным программирова
нием, укажем еще раз на преимущества развитой наследуемости.
409
6.11. Преимущества
развитой наследуемости
Гармония открытости и защиты. Принцип защиты авторского права реализу
ется в естественной гармонии с принципом открытой системы. Последний состо
ит в том, что пользователь не только получает доступ к декларированным воз
можностям системы, но и может перестраивать систему для своих нужд способом,
не предусмотренным явно ее создателем.
Гармония состоит в том, что, с одной стороны, развитый аппарат наследования
позволяет создавать системы, пригодные в сущности для развития в совершенно
произвольном направлении путем пошаговых преобразований, а с другой – ни
одно из этих преобразований не способно нарушить корректное исполнение ста
рых программ (и вообще предоставление услуг, декларированных создателем
программы). Другими словами, идеальная гибкость сочетается с идеальной на5
дежностью (при вполне приемлемой эффективности и близких к оптимальным
трудозатратах за счет потенциального роста тиражируемости программных из
делий).

410
Поддерживаемая развитой наследуемостью технология развития про
граммной системы (пошаговая модификация работающей основы) способству
ет оптимизации и структуризации мышления, программирования, памяти.
Эта технология полностью согласуется с концепцией расслоенного програм
мирования А. Л. Фуксмана [33], описанной им более десяти лет назад (пошаговое
наращивание новых «слоев» работающей версии программы от минимальной ра
ботоспособной основы до цели – разветвленной системы услуг).
Поддерживаемый стиль мышления адекватен естественному развитию
от простого к сложному, от общего к частному, от единого корня к разнообра
зию. (В отличие от классического структурного программирования, подразумева
ющего лишь пошаговую детализацию сверху вниз.)
Говоря более конкретно, развитое наследование обеспечивает расширяе
мость объектов, типов и операций с защитой авторского права и с гаранти
ей сохранности старых программ. Можно воспользоваться программой, разви
вать ее, но нельзя украсть «секреты фирмы», если они скрыты автором.
В заключение подчеркнем существенное отличие изложенного понятия насле5
дования от аналогичного понятия из «реальной жизни». В последнем случае не
только сами типы, но и конкретные экземпляры объектов таких родительских ти
пов, как «животные», «кошки», «собаки» и т. п., реально существуют лишь как
некоторые абстракции, представленные, например, совокупностью генов или
изменчивым набором ныне живущих особей (обладающих, конечно, неисчисли
мым множеством свойств, никак не охватываемых соответствующим типом).
А в информатике и типы, и экземпляры объектов представлены вполне реальны
ми разрядами, записями, фреймами и т. п. Соответственно, и наследование пока
представлено как определение и обработка определений типов, а не результат
«жизнедеятельности» объектов. Однако нет оснований полагать, что так будет
всегда.
Перспективы языков программирования
6.12. Наследуемость и гомоморфизм
(фрагмент математической позиции)
Преамбула. В разделе об авторской позиции было предложено упражнение (по
вышенной трудности) – догадаться, какое известнейшее математическое понятие
непосредственно связано с наследуемостью. Конечно, речь идет о гомоморфизме.
Не исключено, что для активно интересующихся проблемами программирования
математиков такая связь давно очевидна, однако автору не приходилось встре
чать упоминание об этом в литературе.
Открытие связи наследования с гомоморфизмом принадлежит В. А. Левину.
Когда он впервые рассказал на нашем семинаре в МГУ о языке спецификаций Атон
[34], существенно уточняющем и развивающем Vподход [35], я обратил его внима
ние на отсутствие в Атоне аппарата наследования. Для меня особый интерес
к наследованию как к одной из фундаментальных концепций программирования
в этот период был естествен – как раз вызревал соответствующий раздел книги.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
