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

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

.pdf
Скачиваний:
2
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
Наследуемость (к идеалу развития и защиты в ЯП)
А для В. А. Левина, занятого математической формализацией содержательных
концепций спецификации в Атоне, оказалось совершенно естественным понять, какое именно математическое понятие следует сопоставить наследованию. Бук вально на следующий день он предложил мне уже упомянутое упражнение и, вы держав небольшую паузу, выдал результат, после чего мы стали наперебой обсуж дать и другие возможные роли гомоморфизма в программировании. Так что следующие ниже соображения можно считать результатом наших совместных об суждений.
Суть дела. Для начала напомним, что гомоморфизм g из области отправления
Р в область прибытия Q – это отображение
g: P-->Q
типа Р >Q, где Р и Q – алгебры (алгебраические системы), для которого выпол нено определяющее соотношение
g*f= g(f)*g
для всякой операции f из Р (звездочкой обозначена композиция отображений). Для краткости и ясности принято, что все операции унарны. При необходимости nки аргументов nарных операций нетрудно заменить составными объектами, как мы уже поступали в модели Б.
Итак, гомоморфизм – это отображение, сохраняющее свойства всех операций
из области отправления (в том смысле сохраняющее, что, во5первых, каждой из них соответствует некоторый «гомоморфный образ» и, во5вторых, результат каж дой из них отображается в результат применения гомоморфного образа операции к гомоморфному образу ее аргумента).
Основное свойство гомоморфизма можно представить следующей коммута
тивной диаграммой:
411
Результат: f(X) > образ результата: g(f(X))=g(f)(g(X)) /^\ /^\
f: | g(f): | | |
аргумент: Х > образ аргумента : g(X) Коммутативность диаграммы означает, что из ее левого нижнего угла в правый
верхний можно пройти любым из указанных стрелками путей с одинаковым ре зультатом.
Обычно гомоморфизм объясняют как отображение из «богатых» структур
в «бедные», при котором сохраняются алгебраические законы отображаемых структур. Но для нас особенно интересна (обратная, двойственная) интерпрета ция гомоморфизма как отношения между «бедной» и «богатой» структурами, при котором в последней сохраняются все свойства сохраняемых из первой операций. В сущности, это и есть отношение идеального наследования!
Достаточно посмотреть на наш идеал наследования и на определяющее гомо
морфизм соотношение, или на коммутативную диаграмму.
412
Перспективы языков программирования
Действительно, исходным объектам в ее правом нижнем углу соответствуют обо гащенные объекты в левом нижнем углу. Их обогащение проявляется пока лишь в том, что их просто «больше» – каждому обогащенному соответствует «бедный».
Наследовать естественно не все операции (возможности, свойства), а лишь те, которые желательно, поэтому не у всех операций из Q имеются прообразы в левой части диаграммы. Но если чтото наследуется, то результаты «богатых» операций для «бедных» должны «сохраняться» – это и отражено в коммутативности схемы, так как «сохранение» содержательно означает выполнение для прообразов в Р тех же алгебраических законов, что и для их образов в Q.
Наконец, и требованию защиты от разрушения исходных услуг, сохранению работоспособности старых программ, пользующихся этими услугами (и косвенно отсутствие необходимости в перетрансляции и даже запрет на нее – иначе ока жутся под угрозой старые программы!), тоже нашлось место в определяющем го моморфизм соотношении. Ведь не предполагается какойлибо тождественности Р и Q или их частей. В общем случае годится любое отображение, лишь бы выпол нялось основное соотношение. В частности, допустимо и тождественное отобра жение компонент Р и Q, когда оно устраивает.
Итак, гомоморфизм из обогащенного типа в исходный – естественное мате матическое представление (математическая модель) идеального наследования. Примеры обогащаемых и обогащенных типов приводились.
Можно добавить еще одну серию примеров. Как известно, программы на ЯП представляют текстами – последовательностями литер. Будем считать, что опре делен исходный тип «Строчка» – последовательность литер. Его обогащение – тип «Абстрактное_дерево». Объект этого типа представляет собой дерево с листья ми из литер исходной строчки, с доступом к компонентам не только по номерам литер, но и по селекторам, выбирающим поддеревья. Новое обогащение – тип «Синтаксический_объект». Одному абстрактному дереву при этом может соот ветствовать много синтаксических объектов. Например, одно и то же абстрактное дерево может играть роль «строка» или «идентификатор», или даже «буква» (и получить соответствующее значение «синтаксического» атрибута).
Еще одно обогащение – тип «Вхождение_синтаксическогообьекта» – одному идентификатору соответствуют несколько его вхождений (в частности, определя ющее и использующие). Обогащаются, соответственно, и атрибуты. Например, у вхождения может появиться атрибут «координата», которого не было у иденти фикатора. Наконец, другим обогащением синтаксического объекта может слу жить так называемое атрибутированное дерево, например идентификатор с атри бутами «тип», «область видимости» и др.
Упражнение. Опишите соответствующие обогащения на Обероне. Опишите также соответствующие гомоморфизмы.
Подсказка. Гомоморфизм основа: СинтОбъект > АбстрДерево с сохранением операции выборка_поддерева_по_селектору, причем основа(Х.С) = основа(Х).С, где X: СинтОбъект, С: Селектор, a «.» – операция выборки.
Наследуемость (к идеалу развития и защиты в ЯП)
413
Другие применения гомоморфизма в программировании. Важно заметить,
что понятие гомоморфизма удачно моделирует не только
отношение наследова ния типов, но и многие другие важнейшие понятия программирования. Конечно, содержательно все они связаны с тем или иным представлением о «корректном развитии», «корректном обогащении» ранее созданного, рассмотренного, изучен ного и т. п.
Например, метод пошаговой детализации можно считать методом обогаще
ния ранее рассмотренных структур, при котором к ним оказывается применимым все более богатый набор операций. Так, начав создавать пакет в Аде пошаговой детализацией, мы сначала могли лишь переписывать фрагменты программы («пе реписать» – первая операция, применимая к структурамфрагментам), затем получили возможность транслировать спецификацию пакета (вторая операция) и лишь потом исполнять программу (третья операция).
Очевидно и накопление атрибутов при пошаговой детализации. Достаточно вспомнить уже обсуждавшийся переход от текстов к абстрактным деревьям, за тем к синтаксическим, затем атрибутированным.
Подчеркнем, что всегда существует тривиальный гомоморфизм из одних структур в другие, отображающий все мыслимые операции в «ничегонеделание». Мы говорим о естественном гомоморфизме, отражающем содержательное соот ветствие всех существенных операций.
Еще один пример гомоморфизма – связь по импорту в Аде, Модуле2, Обероне и др. Импортирующий пакет служит областью отправления, а импортируемый – областью прибытия. Следующий пример: метод экспортного окна в Обероне. Об ласть отправления – модуль, область прибытия – его спецификация. Последний из этой серии примеров – отображение состояния, построенного в результате
нахождения очередной согласующей подстановки (в реляционном про граммировании) на исходное состояние. Здесь сохраняемые операции – доступ
по именам к значениям.
Какую пользу можно извлечь из изложенных выше соображений?
Во5первых, факт, что казавшиеся совершенно различными понятия програм мирования описываются единым математическим понятием, может принести пользу математической теории ЯП.
Во5вторых, уже сейчас можно классифицировать некоторые языковые конст рукты на «чистые» и «нечистые» с точки зрения идеальной развиваемости в зави симости от того, описывается ли предлагаемое ими развитие естественным гомо морфизмом. Назовем соответствующий критерий ЕГМкритерием. Он работает аналогично тому, как левая факторизация (разделенность) грамматики служит критерием пригодности ЯП для эффективного анализа.
Конечно, и без ясного понимания или формулировки источника несообразно сти во многих случаях интуитивно чувствуется нарушение естественной регуляр ности структур или свойств. Критерий ЕГМ позволяет придать таким оценкам прочную математическую основу.
Например, указатель сокращений «use» в Аде не согласуется с ЕГМ5критерием (оказывается отступлением от естественного гомоморфизма). Не согласуется по
414
тому, что это не чистая вставка контекста, а вставка с «фокусами», нарушающими
естественное отображение имен использующего контекста на имена вставляемого.
Укажем еще один ЕГМфильтр: предикаты с побочным эффектом должны быть отнесены к «нечистым» языковым конструкциям, так как могут «поломать» уточняемое состояние и тем самым сделать невозможным естественный гомомор физм из нового состояния в исходное.
Посредством ЕГМкритерия легко отделить конкретизацию5обогащение от конкретизации5специализации (реализуемой, например, универсальным специа лизатором). Ведь последней позволительно «ломать» исходные структуры спе циализируемой программы, и в отличие от чистой конкретизацииобогащения в случае специализации может оказаться невозможным естественный гомомор физм из новых структур в старые.
Упражнение (повышенной трудности). Опишите точнее все упомянутые гомомор физмы или обоснуйте их отсутствие.
Итак, ЕГМкритерий может оказаться полезным при работе с ЯП, в особенно сти с авторской позиции. Другими словами, получен работающий критерий каче5 ства ЯП в целом и качества отдельных его конструктов.
Перспективы языков программирования
Глава 7
ОбъектноE ориентированное программирование
7.1. Определяющая потребность .. 416
7.2. Ключевые идеи объектно!ориентированного
программирования........................ 417
7.3. Пример: обогащение сетей
на Турбо Паскале 5.5 ..................... 418
7.4. Виртуальные операции ........... 423
7.5. Критерий Дейкстры ................ 431
7.6. Объекты и классы в ЯП
Симула!67 ..................................... 432
7.7. Перспективы, открываемые объектной ориентацией
средств программирования .......... 434
7.8. Свойства объектной
ориентации ................................... 437
7.9. Критерий фундаментальности языковых
концепций ..................................... 438
416
Перспективы языков программирования
7.1. Определяющая потребность
На протяжении всей книги мы неоднократно отмечали потенциальную пользу от введения активных данных, активных обрабатываемых (и одновременно обраба тывающих) объектов. Приводились примеры таких объектов, в частности объек тов задачных типов (процессов) в Аде. Однако активные объекты всегда обладали некоторой экзотикой, всегда были лишены существенных «прав» обычных дан ных, а «обычные» данные – свойств активных объектов.
Например, если пакет в Аде может содержать определения разнообразных программных услуг и с этой точки зрения может считаться как пассивным, так и активным объектом, то он не обладает типом, недопустимы переменныепакеты и т. п. Если допустим задачный тип, то в задаче нельзя непосредственно опреде лить никаких услуг, кроме входов; объекты задачного типа нельзя присваивать (задачные типы считаются ограниченными приватными). С другой стороны, комбинированные типы в Аде не могут содержать процедур в качестве компо нент.
Для ограничений такого рода находятся свои резоны, однако в целом они усложняют ЯП, а по закону распространения сложности – и все, что связано с ним.
Настало время освободиться от неестественных ограничений и подняться на следующий уровень абстракции – ввести концепцию языкового объекта, обобщаю5 щую представление об активных и пассивных объектах. Конечно, это фактически означает, что любой объект придется рассматривать как потенциально активный, если явно не оговорено обратное. Потребность в концепции подобного рода мо жет показаться надуманной, если не учитывать, что она непосредственно связана с потребностью овладеть сложностью программирования, которую лишь частич но удовлетворяют все рассмотренные ранее языковые концепции. Фактически речь идет о потребности рационально сочетать все их преимущества за счет ново го уровня абстракции.
Как мы увидим, эта цель в значительной степени оказалась достижимой на со временном этапе развития программирования в рамках объектноориентирован ного программирования. Выяснилось, что и другие перспективные тенденции в сочетании с ним проявляют всю свою мощь и привлекательность. Иными слова ми, объектноориентированное программирование стало катализатором нового взрыва творческой активности в области ЯП, первые результаты которого в виде практичных коммерчески доступных систем программирования уже появились. Еще более впечатляющие достижения впереди.
Выберем для определенности только один аспект обозначенной нами опреде ляющей потребности, а именно развиваемость, и покажем путь к объектной ори ентации через стремление к идеалу развиваемости, а затем отметим и иные перс пективы, открываемые объектной ориентацией. Конечно, можно было взять за основу и иные аспекты определяющей потребности – наш особый интерес к раз виваемости объяснен в предыдущем разделе.
Объектно/ориентированное программирование
417
7.2. Ключевые идеи объектноEориентированного программирования
Первое фундаментальное изобретение, обеспечившее принципиальные про движения к идеалу развиваемости, состоит в том, чтобы сделать программный модуль нормальным языковым объектом (в частности, объектом вполне опреде ленного типа).
Тем самым в общем случае нормальный языковый объект становится носите
лем программных услуг, а управление такими объектами – управлением предос тавляемыми программными услугами, в частности их развитием и защитой (ведь программный модуль – это носитель одной или нескольких программных услуг, предназначенных для совместного санкционированного использования и защи щенных от использования несанкционированного).
Требуемый уровень гибкости в управлении атрибутами программных модулей
(в частности, видимыми в них именами) достигнут уже в таких ЯП, как Ада или Модула2. Обеспечена определенная гибкость и в управлении типами. Однако из за того, что модули не считаются обычными типизированными языковыми объек тами, для модулей и типов нужны два специальных способа управления, каждый со своими проблемами и, главное, с дополнительными проблемами для програм миста. Унификация этих способов управления открывает путь, в частности, к яс ной и гибкой концепции развиваемости.
Однако когда программный модуль становится объектом некоторого типа,
он остается активным объектом, не только обрабатываемым, но и обрабатываю щим – ведь он носитель программных услуг!
Второе фундаментальное изобретение основано на наблюдении, что актив
ный объект может предоставлять услуги и по извлечению и (или) преобразова нию значений собственных атрибутов – это совершенно естественно для унифи
цированной концепции объекта (и типа). Но в такой ситуации становится удобным ЛЮБУЮ программную услугу (действие, операцию, подпрограмму, ре сурс) рассматривать как принадлежащую конкретному объекту некоторого типа. И управление предоставлением и развитием услуг становится столь же гибким, как управление любыми другими атрибутами объектов (или столь же негибким!).
Так мы приходим к тому, что в последние годы привлекает всеобщее внимание
и получило не слишком удачное название – объектноориентированное програм мирование. Можно надеяться, что читателю теперь понятно происхождение этого названия (в сущности, программировать в этом стиле приходится только услуги, предоставляемые объектами тех или иных типов). Название не слишком удачное, в частности, потому, что такие активные объекты лучше бы называть «субъекта ми», так как они полностью «самостоятельно» определяют свое поведение.
Кстати, остался последний существенный вопрос: определяют на основе какой
информации?
418
Ясно, что в мире активных объектов информация может поступать только от других объектов, обращающихся за услугами (в частности, за «услугой» принять информацию).
Так мы приходим к третьему фундаментальному изобретению, характер ному для объектноориентированного программирования, – концепции обмена
сообщениями между активными объектами, самостоятельно решающими, как именно реагировать на поступающие сообщения, в частности трактовать ли их как запрос
на предоставление услуги другим объектам или просто принять к сведению.
Упражнение. Нетрудно усмотреть близость к изложенным идеям, например, кон цепции адовских объектов задачных типов. Самостоятельно сопоставьте эти кон цепции (возможно, после более подробного знакомства с объектной ориентацией). Найдите аналогии и отличия в других известных вам ЯП.
Итак, в первом приближении мы познакомились с фундаментом объектно ориентированного программирования. Пора привести содержательный пример, демонстрирующий преимущества новой концепции.
Перспективы языков программирования
7.3. Пример: обогащение сетей на Турбо Паскале 5.5
Напишем все тот же пример с обогащением сетей, но на этот раз на ЯП Турбо Паскаль 5.5 – первой из версий широко известной системы фирмы Борланд, в ко торую включены средства объектноориентированного программирования. Име ются в ней и средства раздельной трансляции (появившиеся начиная с вер сии 4.0). Было заманчиво показать объектноориентированное программирование сразу на примере коммерческой системы. Хотя по сравнению с Адой читатель по чувствует наряду с преимуществами и определенные неудобства от возврата к огра ничениям Паскаля (результат функций не может быть составным, после THEN – только простой оператор, нельзя повторять имя процедуры после ее последнего END). Имеются отличия и в концепции модульности (в частности, специфика ция, начинающаяся ключевым словом INTERFACE, отделена от реализации, на чинающейся ключевым словом IMPLEMENTATION, но они не выделены в от дельные трансляционные модули). Подробнее об этом говорить не будем.
UNIT ПараметрыСети; (* модуль с пустой реализацией *) INTERFACE
CONST МаксУзлов = 100;
МаксСвязей = 8; IMPLEMENTATION END.
UNIT УправлениеСетями; INTERFACE
USES ПараметрыСети; (* импорт с доступом по коротким именам *) TYPE
Объектно/ориентированное программирование
Узел = 1..МаксУзлов; ЧислоСвязей = 0..МаксСвязей; Перечень Связей = ARRAY [1..МаксСвязей] OF Узел; Связи = RECORD
Число : ЧислоСвязей; Узлы : Перечень Связей;
END;
ЗаписьОбУзле = RECORD
Включен : BOOLEAN; Связан : Связи;
END; (* до сих пор – традиционно, но ниже следует объектный тип *) (* в Обероне его приходилось моделировать *)
Сети = OBJECT C: ARRAY [1..МаксУзлов] OF ЗаписьОбУзле;
PROCEDURE Инициализировать; PROCEDURE Вставить(X : Узел);
(* у всех процедур – на один параметр меньше! *)
PROCEDURE Удалить(X : Узел); PROCEDURE Связать(АУзел, ВУзел : Узел); PROCEDURE Присвоить(VAR Сеть : Сети); PROCEDURE УзелЕсть(X : Узел) : BOOLEAN; FUNCTION ЕстьСвязь(АУзел, ВУзел : Узел) :BOOLEAN; PROCEDURE ВсеСвязи(X : Узел; VAR R : Связи);
END; (* среди компонент объектов типа Сети – и обычные поля (данные, например поле С), и активные компоненты (операции или правила действий, например Вставить) *)
419
IMPLEMENTATION PROCEDURE Сети.Инициализация; (* такой операции не было, она и раньше была бы полезной, а при работе с объектным типом становится обязательной *)
VAR i: 1..МаксУзлов; BEGIN (* *)
FOR i:= 1 ТО МаксУзлов DO
BEGIN
С[i].Включен := FALSE; С[i].Связан.Число:= 0;
END; END;
(* В объявлениях процедур с префиксом Сети видимы все имена и объявления этого типа, и обращаться к такой процедуре следует как к обычному полю конкретного объекта типа Сети – примеры будут Поэтому во всех операциях на один параметр меньше – не нужно передавать в качестве параметра обрабатываемую сеть. Ведь любая операция работает именно с той конкретной сетью, которой принадлежит. *)
PROCEDURE Сети.УзелЕсть(X : Узел) : BOOLEAN; BEGIN
äàíû íèæå.
420
RETURN С[X].Включен; (* доступ короче – работаем в нужной сети *)
END; (* повторять здесь названия процедур в Паскале нельзя – оцените неудобство! *)
(* хотя, конечно, можно применять комментарии *)
PROCEDURE Сети.ВсеСвязи(X : Узел; VAR R : Связи); BEGIN
R := С[X].Связан;
END; PROCEDURE Сети.Вставить(X : Узел); BEGIN
С[X].Включен := TRUE;
С[X].Связан.Число := 0;
END; PROCEDURE Сети.Присвоить(VAR Сеть : Сети); BEGIN
Ñ := Ñåòü.Ñ;
END;
FUNCTIONСети.ЕстьСвязь(АУзел, ВУзел : Узел): BOOLEAN;
VAR i : ЧислоСвязей;
BEGIN
ЕстьСвязь := FALSE;
WITH С[АУзел].Связан DO
FOR i := 1 ТО Число DO
IF Узлы[i] = ВУзел THEN
ЕстьСвязь := TRUE;
END;
Перспективы языков программирования
PROCEDURE Сети.Связать(АУзел, ВУзел : Узел);
PROCEDURE Установить_связь(Откуда, Куда : Узел);
BEGIN WITH С[Откуда] DO (* вставлен контроль *) IF not Включен THEN write('узел',Откуда,'не включен!'); WITH С[Откуда].Связан DO
BEGIN
IF Число >= МаксСвязей THEN
Write('B узле',Откуда,'нет места для связей'); Число := Число+1; Узлы(Число) := Куда;
END;
END; BEGIN
IF not Есть_связь(АУзел, ВУзел) THEN BEGIN
Установить связь(АУзел, ВУзел, ВСети); IF АУзел < > ВУзел THEN (* "< >" – знак неравенства*)
Установить_связь(ВУзел, АУзел); END;
END;
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]