Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Языки программирования. Концепции и принципы

.pdf
Скачиваний:
2
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
Наследуемость (к идеалу развития и защиты в ЯП)
Вопрос. Можно ли при этом обойтись без перетрансляции модуля Управление_ Сетями?
Подсказка. Ответ очевиден. И даже если предварительно выделить модуль, опре деляющий, например, тип «запись_об_узле» (пренебрегая защитой!), то придется вспомнить о порядке раздельной трансляции.
Замечание. Фактически мы проявили «точку роста» наследуемости, пока, на сколько известно, не использованную авторами ЯП. Аналогично тому, как старые операции воспринимают обогащенные структуры, так и «старые» структуры могли бы воспринимать свои обогащенные компоненты, не требуя переименований и перетрансляций. Точнее говоря, такие «транзитивные обогащения» должны быть допустимыми и без перетрансляции, но последняя, возможно, окажется полезной для оптимизации расхода ресурсов.
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], я обратил его внима ние на отсутствие в Атоне аппарата наследования. Для меня особый интерес к наследованию как к одной из фундаментальных концепций программирования в этот период был естествен – как раз вызревал соответствующий раздел книги.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]