Содержание к диссертации
Введение
1 Поддержка языковыми конструкциями, эволюционного расширения и повторного использования 13
1.1 Операции, обеспечивающие эволюционное расширение 13
1.2 Эволюционное расширение языковых конструкций 18
1.3 Эволюционно расширяемые языковые конструкции, удобные для повторного использования 37
1.4 Место процедурно-параметрического программирования в эволюционной разработке программ
1.5 Перегрузка процедур с одинаковой сигнатурой .'39
1.6 Выводы 41
2 Применение обобщенных записей для эволюционного расширения и повторного использования 42
2.1 Организация обобщений современных языков 42
2.2 Организация и использование параметрического обобщения 54
2.3 Особенности организации обобщенной записи 60
2.4 Базовые операции с обобщенными записями 66
2.5 Использование обобщенных записей с обобщающими , параметрическими процедурами
2.6 Дополнительные возможности, связанные с использованием обобщенных записей 70
2.7 Выводы 73
3 Эволюционное расширение и повторное использование за счет подключаемых модулей 74
3.1 Методы построения модульной структуры программы 74
3.2 Распределение языков по характеристикам модулей 95
3.3 Расширение программы на основе подключаемых модулей 97
4 Особенности процедурно-параметрического программирования 118
4.1 Типичные ситуации расширения написанного кода 118
4.2 Использование ряда парадигм при простых расширениях кода 119
4.3 Реализация объектно-ориентированных образцов с использованием ' процедурно-параметрической парадигмы 130
4.4 Выводы 138
Заключение 139
Список использованных источников 141
- Эволюционное расширение языковых конструкций
- Организация и использование параметрического обобщения
- Распределение языков по характеристикам модулей
- Использование ряда парадигм при простых расширениях кода
Введение к работе
Эволюционная разработка больших программных систем приобретает все большую популярность. Это обусловлено различными факторами, связанными с развитием информационных технологий.
Современные методологии разработки программного обеспечения (ПО) ориентированы на инкрементальное наращивание кода.
Современные системы программирования содержат средства, обеспечивающие поддержку эволюционного проектирования.
Эволюционное расширение программных систем экономически более выгодно, чем использование методов ориентированных на постоянную модификацию уже написанного кода.
Использование эволюционного расширения программ уменьшает количество ошибок, вносимых в написанный и уже отлаженный код, который постоянно приходится модифицировать при использовании традиционных методов разработки программного обеспечения.
В настоящее время предложены разнообразные методы эволюционной разработки ПО. Ряд их широко применяется на практике. Инструментальная поддержка этих методов отражена в различных техниках и парадигмах программирования, среди которых, в частности следует отметить:
объектно-ориентированное программирование (ООП) [ 1 -5];
аспектно-ориентированное программирование (АОП) [6-9];
порождающее программирование [10];
процедурно-параметрическое программирование (ППП) [11, 12] и другие.
Современные инструментальные средства, поддерживающие эволюционную разработку, обладают определенными ограничениями и не всегда позволяют достичь желаемого эффекта. Поэтому, наряду с ними,
широко применяются методы, обеспечивающие инкрементальное
наращивание кода за счет использования разнообразных алгоритмических
приемов. В настоящее время эти приемы отражены в образцах (паттернах)
проектирования, которых стали особенно популярны при использовании
методов объектно-ориентированной разработки программ [13-16].
Среди всего многообразия методов инструментальной поддержки
> эволюционной разработки следует выделить те, которые допускают
повторное использование программных объектов (как уже разработанных
базовых элементов, так и тех, которые обеспечивают их расширение). Это
вносит дополнительную гибкость в создаваемые программные системы и
дает возможность применять отлаженный код при разработке новых
программ. Исследования в области повторного использования кода ведутся
практически для всех технологий программирования.
Вместе с тем следует отметить, что совместное применение методов
эволюционного расширения программ и повторного использования требует
дальнейших исследований. Особенно это касается новых стилей, разработка
которых находится на начальных этапах формирования. К ним можно
отнести процедурно-параметрическую парадигму программирования, для
которой в настоящее время проработаны только основные принципы и
базовые языковые конструкции. Поэтому, исследование методов создания
эволюционно расширяемых и повторно используемых программных
объектов с применением процедурно-параметрического программирования
является актуальной задачей. Для решения этой задачи необходим анализ
методов эволюционной разработки программ и поиск новых путей
расширения кода во взаимосвязи с его повторным использованием. Акцент
на исследование процедурно-параметрической парадигмы и
инструментальных средств, обеспечивающих ее поддержку, в работе сделан
по ряду причин.
111 ill обеспечивает гибкое эволюционное расширение типов данных и процедур, что позволяет безболезненно наращивать программу 'по различным направлениям. В частности, данная парадигма предоставляет гибкую инструментальную поддержку для расширения мультиметодов [17-20]. Однако, возможности подхода, его достоинства и недостатки по сравнению с другими стилями написания кода до конца не исследованы.
Существуют различные способы организации программных объектов, обеспечивающих поддержку процедурно-параметрической парадигмы [21-25]. Однако внутренняя организация данных, используемых при 111111, окончательно не проработана. Не проанализированы особенности, связанные с построением более сложных структур данных, объединяющих воедино обычные записи и обобщения. Отсутствует анализ методов реализации таких типов данных.
Существующая на сегодня модульная организация процедурно-параметрических программ не является достаточно гибкой [20, 26, 27]. Она базируется на традиционной модульной структуре, применяемой в таких языках, как Оберон [29, 30], Оберон-2 [31, 32], Component Pascal [33, 34]. Эволюционная разработка предъявляет дополнительные требования к взаимодействию модулей, что вносит особенности в их реализацию и требует дополнительного анализа.
Цель исследований заключается в необходимости разработки языковых средств процедурно-параметрического программирования, обеспечивающих гибкое эволюционное расширение и повторное использование программных объектов.
Для достижения указанной цели в работе решаются следующие задачи.
1) Для выделения объектов, поддерживающих эволюционного наращивания и повторного использования кода, проводится анализ языковых конструкций, используемых при разработке программного обеспечения. '
Разрабатываются новые языковые конструкции, обеспечивающие дополнительные возможности при построении эволюционно расширяемых и повторно используемых процедурно-параметрических программ. Предлагаются методы реализации этих конструкций. '
Предлагается язык процедурно-параметрического программирования, в котором используются языковые средства, предложенные в работе.
Рассматриваются методы построения эволюционно-расширяемых процедурно-параметрических программ, проводится их сравнительный анализ с подходами, предлагаемыми другими парадигмами программирования.
Методы исследования. В диссертационной работе использовались методы дискретной математики, теории алгоритмов, теории языков и формальных грамматик, теории разработки трансляторов. Для описания синтаксиса языка программирования применялись расширенные формы Бэкуса-Наура. Разработка языка программирования опиралась на методы синтаксического анализа и компиляции, методы объектно-ориентированного и процедурно-параметрического программирования.
Научная новизна и положения, выносимые на защиту.
На основе анализа языковых конструкций разработан метод перегрузки процедур, отличающийся использованием их одинаковой сигнатуры и обеспечивающий эволюционное расширение функциональности без изменения ранее написанного кода.
Предложен эволюционно расширяемый тип данных (обобщенная запись), повышающий эффективность процедурно-параметрического программирования и предоставляющий новые возможности по созданию программных объектов за счет дополнительных полей данных.
Разработан метод организации структуры программы на основе подключаемых модулей, обеспечивающих дополнительную гибкость при эволюционном расширении за счет использования общего пространства имен с родительским модулем.
Предложены новые способы построения процедурно-параметрических программ, использующие разработанные в работе языковые конструкции, что повысило гибкость при эволюционном расширении написанного кода.
Практическая ценность.
Разработан язык процедурно-параметрического программирования, включающий предложенные в работе обобщенные записи и подключаемые модули.
Проведена реализация образцов объектно-ориентированного проектирования с применением процедурно-параметрического подхода, что позволило избавиться от ряда недостатков присущих этим образцам. Предложена реализация ряда образцов проектирования с использованием мультиметодов.
Полученные научные и практические результаты использованы в учебном процессе по дисциплине «Технология программирования» в ФГОУ ВПО «Сибирский федеральный университет».
Достоверность и обоснованность результатов диссертации подтверждаются: исследованием методов эволюционной разработки программ, анализом существующих языковых и инструментальных средств используемых для разработки программного обеспечения, корректным обоснованием постановок задач, точной формулировкой критериев, исследованием и сравнительным анализом существующих подходов к решению поставленной задачи.
Публикации и личный вклад в решение проблемы. По теме диссертации опубликовано восемь научных работ, из которых одна статья в издании по списку ВАК.
Диссертация основана на теоретических, методологических и экспериментальных исследованиях, выполненных на кафедре НейроЭВМ ФГОУ ВПО «Сибирский федеральный университет». Основные теоретические и практические результаты, изложенные в работе, получены либо непосредственно автором, либо с его участием. Автором предложены новые виды программных объектов, поддерживающих эволюционную разработку процедурно-параметрических программ. Им разработаны язык программирования, транслятор, реализована поддержка основных методик процедурно-параметрического программирования, проведены исследования возможных способов реализации процедурно-параметрического подхода.
Апробация работы. Основные положения диссертации докладывались и обсуждались на:
- межвузовских научных конференциях студентов, аспирантов и
*
молодых ученых, Красноярск (2005,2006,2007);
конференции «Технологии Microsoft в информатике и программировании», Новосибирск (2005);
научной конференции молодых ученых «Наука, технологии, инновации», Новосибирск, 2006;
международной конференции «Перспективы систем информатики» (рабочий семинар «Наукоемкое программное обеспечение»), Новосибирск, 2006.
Структура диссертации.
Диссертация состоит из введения, четырех разделов, заключения, списка литературы и двух приложений. Работа содержит 150 страниц
основного текста, 21 рисунок, три таблицы. Список литературы содержит 9/5 наименований.
Во введении представлены цели и задачи исследования, раскрывается актуальность, новизна полученных результатов, практическая значимость. Приводится перечень вопросов, выносимых на защиту.
В первом разделе представлен анализ методов и приемов, используемых для разработки эволюционно расширяемых и повторно используемых программ. Проведена их систематизация, при которой учтены виды программных объектов и способы их расширения. На основании этого выделены конструкции, допускающие эволюционное расширение кода наряду с его повторным использованием. Предложен метод перегрузки процедур с одинаковой сигнатурой, обеспечивающий эволюционное наращивание функциональности программы.
Во втором разделе анализируются программные объекты, обеспечивающее эволюционное расширение и повторное использование при процедурно-параметрическом программировании. Предлагаются синтаксис и семантика обобщенных записей, объединяющих в единую языковую конструкцию обычную запись и процедурно-параметрическое обобщение. Это позволяет повысить гибкость при эволюционном расширении данных. В частности, появляются такие возможности, как рекуррентное определейие цепочек обобщенных записей, использование обобщений, недоступных для расширения клиентским частям программы, и обобщений, расширяемых клиентом. Рассматриваются методы реализации обобщенных записей.
В третьем разделе исследуются модульные структуры программ. Наращивание самих модулей не оказывает прямого воздействия на функциональность, но обеспечивает единое пространство имен с ранее созданными фрагментами. Это облегчает понимание процесса добавления кода. Более важным является поддержка модулем расширяемости других
программных объектов, играющих основную роль в наращивании и
повторном использовании: типов данных, процедур, классов. Для поиска
перспективных модульных структур выделены основные характеристики,
которые определяют варианты построения модулей. Сделан вывод о том, чтр
расширение уже созданных программных объектов нужно проводить в
специализированных подключаемых модулях, расширяющих
соответствующие им родительские модули, которые содержат обобщения и обобщающие параметрические процедуры. Предлагается синтаксис и семантика подключаемых модулей. Также приводятся внутренние структуры данных, формируемые при компиляции подключаемых модулей и используемые во время компоновки. Помимо этого, на примере мультиметода показано, каким образом изменились структура программы и область действия имен по сравнению с реализацией этой же программы на языке 02М [19,35].
Четвертый раздел посвящен особенностям использования 111111 для построения эволюционно расширяемых программ. На основе проведенных исследований языковых конструкций разработан язык программирования, ориентированный на поддержку только процедурно-параметрической (ПП) парадигмы. Это позволило провести сравнение данного стиля с другими парадигмами.
Для первоначального анализа эволюционных возможностей парадигм программирования были выбраны простые ситуации. Показано, что ПЦ парадигма по возможностям эволюционного расширения превосходит процедурный и 00 подходы вместе взятые. Это позволяет судить о высоком потенциале 111111.
Для анализа возможностей процедурно-параметрической парадигмы в более сложных ситуациях были выбраны образцы 00 проектирования. Они представляют наиболее удачные 00 решения, широко используемые в
/
разработках больших программных систем. Поэтому было интересно сравнить, что позволяет ППП при их реализации. Процедурно-параметрическая реализация различных 00 образцов по аналогии показала, , что получаемый код обладает сходными свойствами, а гибкость процедурно-параметрического стиля позволяет избавиться от многих недостатков, присущих 00 решениям.
Наряду с реализацией по аналогии, ориентированной на копирование 00 механизма передачи сообщений по цепочке классов, ПП подход для ряда образцов допускает прямую реализацию на основе мультиметода. Показано, что это ускоряет создание кода при небольших увеличениях времени на обработку параметрических отношений при выполнении программы.
В приложении А приведено описание процедурно-параметрического , языка программирования.
В приложении Б приводятся исходные тексты примера процедурнб-параметрической реализации образца проектирования «Абстрактная фабрика».
В заключении перечислены полученные результаты, обоснована перспектива их применения и развития, сформулированы выводы по диссертационной работе.
Эволюционное расширение языковых конструкций
Развитие языков программирования вызвано стремлением повысить эффективность кодирования. Оно является актуальным из-за постоянно увеличивающихся размеров и сложности программ. Чтобы упростить процесс разработки, в языки стали вводиться различные понятия, обеспечивающие модульность и иерархию. Это позволило разделить процесс разработки, сделать его иерархическим, что сгладило проблему размерности и скрыло особенности реализации той или иной операции от клиентов. Появились конструкции, отвечающие за определенные функциональные характеристики и обладающие специфическими свойствами. Их расширение также обладает особенностями и базируется на определенных приемах. При этом не все эволюционно расширяемые программные объекты допускают повторное использование.
Современная программа является композицией разнообразных конструкций, среди которых можно выделить. 1) Объекты данных, обрабатываемые программой. К ним можно отнести простые и составные типы, используемые для построения переменных. 2) Алгоритмические конструкции, используемые для обработки имеющихся данных. Сюда входят базовые операции, выражения, операторы, блоки операторов, процедуры и функции. 3) Комбинированные конструкции, используемые для группировки данных и алгоритмов. Они обеспечивают наглядное представление программных объектов, а также позволяют создавать крупные функционально законченные переносимые единицы. Эти понятия представлены классами, модулями и самими программами.
Выделение методов эволюционного расширения программ, хорошо согласующихся с повторным использованием, проводится на основе анализа, и группировки языковых конструкций, применяемых для эволюционного расширения программ. Это позволяет выявить понятия, удовлетворяющие требуемым критериям. В качестве основного критерия можно выделить методы расширения программных объектов, которые дополнительно можно разбить на следующие группы: - методы эволюционного расширения на уровне данных; - методы эволюционного расширения на уровне алгоритмических конструкций; - методы эволюционного расширения на уровне комбинированных понятий.
Данные разделяются на базовые и составные типы. Алгоритмические составляющие делятся на базовые операции, выражения, операторы, блоки операторов, процедуры. К комбинированным объектам можно отнести классы, модули, программы. Каждое из представленных понятий обладает своими синтаксическими и семантическими особенностями и в разной степени допускает эволюционное расширение. Помимо этого отличаются и свойства рассматриваемых понятий по повторному использованию.
Базовые типы предоставляются языками программирования как фундаментальные понятия. Их совокупность определяется при разработке. Точно также определяются и операции обработки базовых типов. Они , считаются преопределенными, что предполагает невозможность их изменения (включая и эволюционное расширение). 1.2.2 Эволюционное расширение составных типов данных
Составные типы создаются программистом из любых существующих типов. Они могут изменяться с применением любых приемов в ходе последующей модификации программы. Поэтому, данный вид понятий является одной из основных конструкций, допускающих поддержку критериев расширяемости и повторного использования. Дополнительно составные типы данных обычно делятся на агрегаты и обобщения [1, 46]. Агрегаты предназначены для формирования неделимого понятия.
Инструментальная поддержка эволюционного расширения типов данных широко используется в настоящее время и достигается различными путями. Среди них можно выделить следующие варианты: - расширение типа или наследование; - обобщение типа; - использование шаблонов. » Расширение типа Термин «расширение типа» введен Виртом [47]. Он использован при описании процедурного языка программирования Оберон [29, 30, 43] и является эквивалентом термина «наследование», применяемого в ООП [2]. В частности, в языке программирования C++ допускается построение чистых структур данных с применением наследования [48]. Расширение типа используется для построения новых агрегатов из существующих понятий. Однако наряду с обычным агрегированием оно также обеспечивает сохранение информации о своем базовом типе. В языке программирования Ada-95 [49] расширение типов реализовано с использование тэговых типов и по специфике совпадает с типами языка программирования Оберон.
Существует два способа использования этой информации: статический и динамический. При статическом способе информация о базовом типе берется из таблиц компилятора, что позволяет применять принцип подстановки [2], заключающийся в подмене базового типа любым расширяющим (производным) типом. Этот метод в ООП является традиционным для реализации не только агрегатов, но и для построения обобщений. Он обеспечивает подключение к указателю на базовый класс любых производных классов, что, наряду с использованием виртуальных методов, позволяет использовать полиморфизм при обработке альтернативных ветвей программы. При этом добавление новых альтернативных классов не ведет к изменению написанного кода и позволяет безболезненно расширять вариантные фрагменты программной системы.
В языке Оберон расширяемые записи являются единственным способом создания обобщений и обеспечивают безболезненное наращивание альтернатив. Однако, отсутствие механизмов наращивания процедур, аналогичного 00 виртуальным методам, не позволяет расширять вариантные фрагменты программ. В ситуациях, когда подобное расширение необходимо, предлагается использовать алгоритмические приемы [43].
Динамическая информация о базовом типе непосредственно встраивается в структуру данных и позволяет определить свой тип во время выполнения программы. Существуют различные пути реализации этого механизма. В языке программирования Оберон используются дополнительные переменные и указатели в самом расширяющем типе [47]. В языке программирования C++ данный механизм реализован через виртуальный метод. В C++ он начинает работать только при компиляции программы с подключением режима информации о типе во время выполнения (Run Time Type Information или RTTI). В более поздних 00 языках, таких как Java [50] и С# [51-53] динамическая проверка типов с использованием таблиц виртуальных методов изначально встроена в классы.
Расширение типов обеспечило эволюционное наращивание и повторное использование обобщений за счет динамического связывания переменных через указатель на базовый тип. Это позволило отказаться от вариантных записей и объединений, существующих в процедурных языках. В сочетании с виртуальными методами [2] и связанными процедурами расширения типов обеспечили эффективную поддержку эволюционной разработки программ за счет использования полиморфизма типов и процедур. При этом применение процедур, связанных с типом позволило реализовать 00 подход в рамках процедурной терминологии [31,32].
Организация и использование параметрического обобщения
В ППП [11, 12] для эволюционного расширения используется параметрическое обобщение. Целью его создание было повышение гибкости формирования альтернатив, то есть тех программных конструкций, на которые ложится основной груз по эволюционному расширению. Вместе с обобщающими процедурами параметрические обобщения обеспечивают гибкое добавление новых типов данных и процедур.
Специализация = (идент ":" (Тип NIL) ) Тип. Существуют различные варианты построения внутренней структуры обобщения, специфической особенностью которых является наличие уникального локального признака для каждой специализации, используемого во время выполнения программы. Наряду с локальными признаками на этапе компиляции могут использоваться дополнительные логические признаки, определяемые способом идентификации специализаций. Локальность признаков позволяет формировать их в ограниченном диапазоне, начинающимся с нуля. Это в дальнейшем обеспечивает легкую привязку к массивам, определяющим параметрические соотношения. Именно локальная идентификация альтернатив и поддержка при этом эволюционного расширения отличают параметрическое обобщение от других обобщений.
Можно выделить два основных способа реализации переменных на основе описания параметрических обобщений: - использование основы, к которой подключается специализация, идентифицируемая соответствующим значением признака; - использование специальных монолитных конструкций, каждая из которых содержит одинаковую основу, определяющую обобщение и отличается от другой признаком специализации и дополнительной областью памяти, задающей специализацию.
Обобщенная переменная с разделением основы и специализации Этот подход обладает высокой гибкостью, обеспечивая не только статическое формирование обобщений во время компиляции, но и динамическое связывание, используя при этом уже существующие переменные, задающие основы специализаций. То есть, окончательную структуру обобщения можно формировать в ходе выполнения программы. ,
Если специализированная переменная задается статически, выделение памяти под основу и специализацию, и установление между ними связи, могут осуществиться на этапе компиляции. При манипуляции с такой переменной компилятор может непосредственно использовать адрес специализации, что ускоряет обработку. Помимо этого, данный метод реализации полностью соответствует метафоре параметрического обобщения как конструкции, используемой для подключения различных альтернатив.
Отсутствие монолитной структуры объекта может считаться недостатком, который проявляется при работе с динамическими структурам данных, то есть в той области, где используются языки, с гибкой поддержкой полиморфизма и эволюционным расширением программ. Создание переменных приходится проводить в два этапа, а использование статической структуры основы обобщения без специализации в большинстве случаев выглядит нецелесообразным. Неизвестно, сколько таких основ должно быть в программе, когда они могут быть использованы. В принципе, основу и специализацию можно создать за один раз, расположив друг за другом. Но в этом случае сформированная структура будет отличаться от монолитной реализации только наличием избыточного адреса, для инициализации которого необходимы дополнительные операции. Поэтому, в языках со строгой типизацией, в основном используемых при разработке больших программных систем, целесообразнее применять специализированные переменные, реализованные в виде единого конструктива.
Обобщенная переменная с основой и специализацией в одной структуре Использование таких структур ускоряет создание обобщенных переменных, облегчает контроль ссылок и сборку мусора за счет упрощения структуры программных объектов. Данный подход не совпадает ъ идеологией типа, уточняемого специализациями. Но отличие внутренйей реализации от внешнего восприятия не является важным и прозрачно для разработчиков программ. Представленный способ построения специализаций близок к реализациям, основанным на расширении типов и наследовании. Основное отличие проявляется в способе идентификации, который в параметрических обобщениях является локальным вместо глобального контекста, используемого в расширяемых типах и классах.
При создании конкретной фигуры приходится формировать не только объект типа ColoredFigurel, но и специализацию, на которую указывает указатель типа PFigure. Помимо этого, приходится связывать экземпляры данных этих двух типов во время выполнения программы. Представленный пример показывает, что разработка типа данных, обладающего как свойством обычной записи, определяющей множество агрегативных полей, так и свойством обобщения, задающего альтернативные расширения этой записи, является целесообразным развитием средств 111111.
Для расширения возможностей процедурно-параметрического программирования предлагается ввести обобщенные записи [71-76], которые обеспечивают более гибкое использование параметрических обобщений за счет включения структуры последних в обычную запись.
Разработка данного понятия затрагивает как его внутреннюю, структуру, используемую для создания переменных, так и синтаксис языка программирования, который, в рамках проводимых работ должен быть дополнен новыми правилами. Полный синтаксис предлагаемого процедурно-параметрического языка программирования приведен в приложении А. Синтаксис обобщенной записи, расширяющей структуру параметрического обобщения, выглядит следующим образом:
Распределение языков по характеристикам модулей
Для обеспечения гибкого эволюционного расширения программных объектов с сохранением существующих интерфейсов между модулями предлагается использовать подход, базирующийся на дополнительных модулях-расширителях, подключаемых к основному модулю [77]. Идея подхода схожа с использованием механизма наследования вместо прямого включения типов во вновь формируемый программный объект. В случае с наследованием формируется описание нового типа, расширяющего базовый тип дополнительными полями. За счет принципа подстановки осуществляется использование метода производного класса, который может обрабатывать свои собственные дополнительные данные.
При использовании подключаемых модулей ситуация отличается тем, что вместо множества экземпляров базового и производного классов в программе существуют только по одному экземпляру разных модулей. Поэтому данный метод не является аналогом наследования. Вместо этого, разработанное расширение модуля может подключаться к уже существующему базовому модулю, образуя вместе с ним единое пространство имен. Это отличает подключение модуля от его импорта, при котором внутренние пространства имен модулей не пересекаются. Подключаемый модуль может импортироваться из других модулей, обеспечивая им передачу как своего интерфейса, так и интерфейса расширяемого модуля. Однако использование этого модуля вместо базового не является основной спецификой, так как не обеспечивает неизменность клиента, использующего базовый модуль.
Главной особенностью подключаемого модуля является возможность расширения обобщенных записей и обобщающих параметрических процедур. Она достигается за счет описания в подключаемом модуле специализаций и их обработчиков.
Отличие от обычного модуля проявляется в указании имени модуля-родителя (базового модуля) в круглых скобках. Знак « » или «+» за именем родителя определяет права доступа к интерфейсу родительского модуля из модулей, импортирующих подключаемый модуль. Правила использования этих знаков совпадают с их применением в языке программирования Оберон и более подробно описаны в приложении А.
Практически это позволяет организовать прямые взаимодействия, отличающиеся от существующих языковых структур, используемых в языках Оберон, Оберон-2, 02М. В качестве примера можно рассмотреть, каким образом будет формироваться структура обобщений в программе, осуществляющей работу с простыми геометрическими фигурами, описанную для языка 02М в [19, 20].
Использование дополнительных модулей-расширителей изменяет внутреннюю организацию программы и ее построение по сравнению с традиционными модульными подходам. Как родительский модуль, так и подключаемые модули порождают в ходе компиляции несколько единиц объектного кода: - основную единицу, содержащую реализацию процедур и память, отводимую под переменные модуля; - интерфейсную единицу, обеспечивающую описание внешних ссылок, экспортируемых в другие модули; - параметрическую единицу, обеспечивающую связь обобщающих процедур родительского модуля с обработчиками специализаций размещенных как в подключаемых модулях, так и в самом родительском модуле.
Помимо этого, в зависимости от режима компиляции, подключаемый модуль может порождать дочернюю единицу, предоставляющую интерфейс для подключения расположенных в нем специализаций и их обработчиков к параметрической единице родительского модуля, содержащей соответствующие обобщения и обобщающие процедуры. Общая структура единиц кода, порождаемых при компиляции, приведена на рисунке 3.4.
Основная единица по своей внутренней организации практически не отличается от единиц кода, порождаемых модулями других языков программирования. Ее адресное пространство распределено между областями данных и кода. В области данных находятся переменные, описанные в модуле. В области кода размещаются все процедуры модуля. Обобщенные параметрические процедуры содержат код, обеспечивающий обращение к таблицам параметрической единицы, в которых, во время компоновки происходит формирование окончательных связей со специализациями. Методы их формирования могут быть разнообразными, что показано в [21, 23,24]. В этой же единице размещается код обработчиков специализаций, связанный с обобщенными процедурами, как своего модуля, так и с обобщенными процедурами вышестоящих родительских модулей.
Состав и структура выходных единиц, порождаемых при компиляции модуля Интерфейсная единица содержит ссылки на экспортируемые программные объекты модуля, размещенные в основной единице. Это могут быть адреса переменных, обычных процедур, обобщающих параметрических процедур. Помимо этого в ней описываются имена и видимая часть структур экспортируемых типов. Реализация интерфейсной единицы может быть различной, что не играет принципиальной роли. Обычно она может рассматриваться как подмножество самого языка программирования, реализовываться на известных языках данных, например, XML [90], использовать язык описания интерфейсов, такой как IDL [91], или задаваться в специфичном внутреннем представлении. Интерфейсные единицы используются в большинстве модульных языков, например, Обероне, Ada, Модуле-2, Component Pascal.
Параметрическая единица определяется спецификой процедурно-параметрического программирования. Она используется для обеспечения процесса раздельной компиляции модулей и содержит параметрические таблицы, определяющие подключение обработчиков специализаций к обобщенной параметрической процедуре. При компиляции родительского модуля эти таблицы могут быть пустыми или заполняться только ссылками на обработчики специализаций, размещенные в самом модуле. Окончательное заполнение таблиц осуществляется в ходе компоновки, обеспечивающей подключение информации, размещенной в дочерних единицах. Компоновка может быть статической или динамической, выделяясь, тем самым в отдельный проход или интегрируясь с процессом начальной загрузки программы. Помимо этого возможно выполнение компоновки подключаемых модулей во время компиляции программы.
Дочерняя единица порождается для подключаемых модулей. В ней содержится ссылочная информация об обработчиках специализаций, которая предназначена для использования во всех родительских модулях. То есть в общем случае подключаемый модуль может создавать обработчики специализаций не только для своего родительского модуля, но и для модуля, который является родителем его родителя и т.д. Поддержка иерархии родителей обеспечивает гибкое расширение программы, не вынуждая программиста создавать подключаемые модули, непосредственно связанные с родительскими. Это хорошо согласуется с использованием общего пространства имен по всей цепочке подключения и позволяет использовать переменные, появившиеся при более поздних подключениях в обработчиках специализаций обобщенных параметрических процедур, созданных в выше стоящих родительских модулях.
Использование ряда парадигм при простых расширениях кода
Использование при процедурном подходе расширений типов данных обеспечивает безболезненное добавление новых специализаций в языках программирования Оберон, Оберон-2, Component Pascal. Аналогичным образом проблема расширения решается в языке C++ применением наследования к структурам. Использование более ранних процедурных языков ведет к применению объединений (в языке С) или вариантных записей (язык Pascal), что не обеспечивает эволюционного расширения. Эту задачу в старых языках можно решить использованием указателя на произвольный тип данных, что снижает безопасность программного кода. То есть, появляется вероятность некорректного использования этого указателя. Добавление специализаций ведет к необходимости добавления их обработчиков, которые обычно реализуются как ветви условного оператора или эквивалентного ему оператора выбора (переключения). То есть, приходится непосредственно изменять этот оператор во всех процедурах, осуществляющих обработку альтернативных специализаций обобщения. Это говорит о том, что процедурный подход не обеспечивает эволюционнрго расширения в данной ситуации. Объектно-ориентированный подход Для добавления специализации применяется расширение типа (в Оберон, Оберон-2 и Component Pascal) или создание производного класса (в C++, С#, Java и т.д.). Для добавления новых обработчиков специализаций используются процедуры, встроенные в тип, в языках семейства Оберон. В общепринятых 00 языках используются виртуальные методы добавляемых производных классов. Процедурно-параметрический подход Обеспечивает добавление новых специализаций через подключение специализации к обобщению за счет существующей в языке внешней операции добавления. Необходимые обработчики специализаций также легко расширяют уже существующие обобщающие процедуры за счет дописывания новых реализаций.
Процедурный и процедурно-параметрический подходы Использование внешних процедур обеспечивает простое решение данной задачи. Не возникает никаких проблем с их безболезненным добавлением в новых единицах компиляции. Объектно-ориентированный подход Необходимо добавить новый метод внутрь класса, что ведет к изменению интерфейса последнего. Сама реализация метода может быть описана в ряде языков (C++) в новой единице компиляции, что уменьшает объем изменений вносимых в программу. Однако большинство современных 00 языков (С#, Java), ориентированных на автоматическую генерацию интерфейсов модулей, используют запись реализации методов внутри класса, что ведет к дополнительным изменениям уже написанного кода. Однако даже при изменении только интерфейса, если изменяемый класс является базовым, произойдет перекомпиляция всех модулей, которые зависят как от этого класса, так и его производных классов. Поэтому, при разработке объектно-ориентированных программ рекомендуется большее внимание уделять методам, образующим интерфейсы классов, чтобы впоследствии избежать их изменения [1]. Таким образом, 00 парадигма не поддерживает эволюционного добавления новых методов, расширяющих функциональность класса.
Прямое добавление новых полей в запись или класс ведет к его изменению независимо от избранного стиля программирования. Это обусловлено тем, что тип объекта во всех случаях должен оставаться неизменным. Поэтому, при необходимости модификации типа с сохранением возможности безболезненно расширять программу, используются обходные пути, опирающиеся на косвенное связывание. В этом случае осуществляется связывание через указатель на базовый тип, к которому подключается объект производного типа, содержащий дополнительные поля.
Для добавления полей косвенным связыванием удобно использовать расширения типов в языке программирования Оберон. В этом случае новый тип расширяет предшественника и через указатель на базовый тип может использоваться по целевому назначению. Реализованный в языке механизм проверки типа позволяет легко перейти от базового типа к производному. Поэтому можно считать, что более поздние процедурные языки поддерживают добавление новых полей используя полиморфизм типа.
Вместе с тем, следует отметить, что более ранние процедурные языки (С, Pascal) не обеспечивают реализацию подобного приема из-за отсутствия дополнительной инструментальной поддержки проверки типа. Поэтому их использование в таком режиме невозможно без написания дополнительного кода.
Объектно-ориентированный подход Добавление новых полей, как и при расширении типов, осуществляется в производном классе. При использовании языков, поддерживающих динамическую идентификацию типа во время выполнения (С#, Java, Object Pascal [93]), использование классов аналогично применению расширений типов, описанному для процедурных языков. В этом случае, наряду с данными можно включать и новые методы, осуществляющие их обработку. Вместе с тем, следует отметить, что использование явной проверки типа во время выполнения в большей степени является процедурным, чем объектно-ориентированным приемом, так как объектно-ориентированный полиморфизм базируется на сочетании наследования и виртуализации.
При отсутствии RTTI или отключения этого режима (например, в C++) использование косвенного добавления полей в класс становится невозможным. Для его реализации необходимы дополнительные алгоритмические приемы, например, включение в базовый класс виртуального метода, переопределяемого в производных классах, за счет чего обеспечивается требуемая идентификация. Таким образом, использование объектно-ориентированного полиморфизма в данной ситуации невозможно без дополнительной алгоритмической поддержки.
Использование косвенного добавления полей допускается при использовании обобщенных записей. В этом случае расширение заключается в добавлении специализации. Доступ к специализации поддерживается механизмом проверки признака специализации, по которому возможно установить ее тип.
Как и в ранее описанных подходах, в данном случае также имеются свои ограничения, которые определяются тем, что в разрабатываемой версий языка, наряду с обобщенными записями присутствуют и обычные записи. Это сделано для повышения эффективности работы с простыми структурами данных. Однако обычные записи нельзя уточнять, что ведет к невозможности расширения их полей за счет косвенного добавления. Проблему можно решить, оставив в языке только обобщенные записи. Но целесообразность этого решения требует дополнительных исследований.