SoftCraft
разноликое программирование

Top.Mail.Ru

Языковая поддержка эволюционного расширения процедурных программ

© А.И. Легалов, Д.А. Швец

Примеры, демонстрирующие эволюционное расширение мультиметодов (~ 27 КБайт)

Ссылка на головную страницу проекта

Предлагаются языковые конструкции, поддерживающие процедурно-параметрическое программирование, что повышает возможности эволюционного развития уже написанного кода. Приводится пример их реализации в качестве расширения языка программирования Оберон-2.

Введение

Современная разработка программных систем характеризуется инкрементальным расширением кода при выпуске очередной версии продукта. Итеративность обусловлена неполным знанием структуры будущей программы во время проектирования и начальной эксплуатации, необходимостью скорейшего выхода на рынок, а также другими факторами. Стремление поддержать эволюционное программирование привело к тому, что процедурный подход не выдержал конкуренции с объектно-ориентированным стилем, на плечах которого выросла целая индустрия проектирования. Несмотря на отсутствие гибкости при непосредственном добавлении новых процедур и использовании множественного полиморфизма, объектно-ориентированный (ОО) подход успешно справляется со многими задачами. Там, где трудно применить очевидные решения, на помощь приходит опыт, накопленный в рамках удачных проектов. В настоящее время он фиксируется в паттернах проектирования [1].

Вместе с тем, можно отметить, что зачастую процедурные программы обладают более простой и понятной структурой. Раздельное формирование данных и функций обеспечивает быстрое создание исходного приложения за счет независимости его составляющих. Нет особой необходимости в тщательном продумывании интерфейсов объектов. В целом на ОО проектирование требуется больше времени, чем процедурное [2].

Основной проблемой процедурного подхода является неудовлетворительная поддержка эволюционного расширения программ. Добавление новых альтернатив в существующий код ведет к большим переделкам уже написанных процедур, что обуславливается включением фрагментов, обеспечивающих обработку новых вариантов. Вместе с тем, создание альтернативных конструкций широко используется в программировании, так как позволяет изменять и наращивать функциональность программы без модификации интерфейса с клиентскими приложениями. Существующие попытки внедрить эволюционное проектирование, например, на основе технологии вертикального слоения [3, 4], были остановлены на концептуальном уровне и не вылились в создание соответствующих инструментальных и языковых средств.

Вместе с тем, простота процедурного подхода и легкость моделирования с его помощью эволюционных расширений даже тех конструкций, с непосредственным построением которых не справляется ООП [5, 6], наводит на мысль, об его использовании в проектировании. Для этого нужно преодолеть основной недостаток: невозможность безболезненного расширения процедур при добавлении альтернативных структур данных.

Основные идеи, связанные с решением этой проблемы, были изложены в описании процедурно-параметрической парадигмы программирования [7]. Ее развитие вылилось в создание расширений языка Оберон-2 [8, 9], применение которого позволяет рассматривать эволюцию программы как вспомогательный процесс, слабо связанный с ее исходной структурой. Программирование может осуществляться с использованием общепринятых канонов процедурного подхода. Язык включает все возможности Оберона-2 [9], что позволяет использовать ранее написанные модули.

Поддержка процедурно-параметрической парадигмы

Поддержка новой парадигмы обеспечивается использованием следующих понятий.

1. Специализация. Любой абстрактный тип, используемый для построения программных объектов: переменных, указателей на процедуры и т.д., рассматриваемых как экземпляры специализаций.

2. Параметрическое обобщение. Состоят из специализаций, каждая из которых определяет одну альтернативную структуру данных, используемую в обрабатывающих ее процедурах. Специализации однозначно идентифицируются с помощью признаков. В общем случае они могут быть одного типа. Обобщения используются в качестве формальных параметров. Специализации подставляются в качестве конкретных аргументов (фактических параметров) во время вычислений.

3. Обобщающие параметрические процедуры. Определяют сигнатуры обработчиков параметрических обобщений, задают обработку по умолчанию. Используются в качестве интерфейсных процедур, доступных клиентской части программы. Скрывают истинную природу динамически связанных переменных, обеспечивают поддержку множественного полиморфизма.

4. Обработчики параметрических специализаций. Обеспечивают непосредственную манипуляцию конкретными специализациями, включенными в состав параметрических обобщений. Автоматически выбираются после вызова обобщающей процедуры в зависимости от значений параметрических аргументов, подставляемых вместо формальных параметров, описанных в обобщающей параметрической процедуре.

5. Экземпляры параметрических обобщений. Представляют переменные, обрабатываемые в ходе вычислений. Каждый экземпляр задает конкретную специализацию, объявленную статически или полученную динамически в ходе вычислений. Используются в качестве фактических параметров при вызове обобщающих параметрических процедур.

6. Вызовы параметрических процедур. Обеспечивают доступ к отдельным обработчикам специализаций через единые сигнатуры, определяемые обобщающими параметрическими процедурами.

Использование процедурно-параметрического подхода рассмотрим на примере простой обработки геометрических фигур. Исходными данными для написания программы являются прямоугольник и треугольник:

(* Прямоугольник со сторонами x и y *)
Rectangle = RECORD x, y: INTEGER END
(* Треугольник со сторонами a, b и c *)
Triangle = RECORD a, b, c: INTEGER END

Организация параметрических обобщений

Параметрическое обобщение является новым абстрактным типом. За основу его структуры взята форма, используемая в вариантной записи языка программирования Модула-2 [10]. В связи с ее отсутствием в Обероне-2, интеграция с существующим синтаксисом проблем не вызывает. Параметрическое обобщение описывается следующим правилом:

Обобщение = RECORD CASE [ TYPE ] [ LOCAL ] OF
    [ СписокСпециализаций ] [ ELSE Специализация ] END .
СписокСпециализаций = Специализация { "|" Специализация } .
Специализация = ( СписокПризнаков ":" ( Тип | NIL ) ) | Тип .
СписокПризнаков = Признак [ "," Признак ] .
Признак = Идентификатор .

При отсутствии спецификатора TYPE предполагается явное задание признаков. В этом случае допускается использование в разном контексте специализаций с одинаковыми типами. В качестве примера рассмотрим построение обобщения для трех фигур: треугольника, прямоугольника и ромба. Ромб будет задаваться своими диагоналями, с использованием структуры, разработанной для прямоугольника. Первая версия обобщения будет выглядеть следующим образом:

(* Первое обобщение фигуры *)
Shape1 = RECORD CASE OF 
  trian: Triangle | 
  rect, rhomb: Rectangle 
END

После ключевого слова ELSE может следовать специализация, устанавливаемая по умолчанию в случае, когда при описании переменной ее значение явно не указано. Предположим, что в новом обобщении треугольник будет специализацией по умолчанию. Тогда его структура примет следующий вид:

(* Второе обобщение фигуры *)
Shape2 = RECORD CASE OF 
  rect, rhomb: Rectangle 
ELSE
  trian: Triangle
END

После признака можно использовать пустое значение NIL, сигнализирующее об отсутствии привязки к какому-либо типу. Это позволяет воспринимать признаки как эквивалент перечислимому типу, сохраняя при этом возможность их подстановки в качестве аргументов при вызове параметрических процедур. Например, таким образом можно задать дни недели:

(* Дни недели *)
Day = RECORD CASE LOCAL OF 
  Sunday, Monday, Tuesday, Wednesday, 
  Thursday, Friday, Saturday: NIL 
END

Ключевое слово LOCAL используется для создания нерасширяемых обобщений. При его отсутствии обобщение может быть дополнено новыми специализациями, в том числе и в других модулях. Окончательная структура обобщения и непротиворечивость в этом случае определяются только в момент компоновки программы.

Ключевое слово TYPE применяется при неявном задании признаков, имеющем следующее толкование: имя признака совпадает с именем типа. Следовательно, допустимо только описание типов, и каждый тип должен быть уникальным. Идентификация осуществляется только по имени типа. В этом случае нельзя непосредственно вводить альтернативу для ромба. Поэтому приходится обобщать только две фигуры:

(* Третье обобщение фигуры *)
Shape3 = RECORD CASE TYPE OF 
  Triangle | Rectangle 
END

Обобщение можно изменить добавлением новых специализаций в модулях, импортирующих исходное объявление. Расширение можно использовать и в том модуле, где объявлено обобщение. Его синтаксис имеет следующий вид:

Расширение = Обобщение "+=" СписокСпециализаций.

Использование можно продемонстрировать примером:

(* Круг радиуса r *)
Circle = RECORD r: INTEGER END
(* Расширение обобщения *)
Shape3 += Circle

Существует возможность описания указателей на обобщение, синтаксис которых полностью соответствует описанию указателей в языке Оберон-2. Они могут использоваться для связи с динамическими переменными любой специализации, обеспечивая, тем самым, поддержку полиморфизма.

Обобщенные переменные

Обобщенные переменные не могут создаваться статически, так как невозможно определить их размер во время компиляции. Поэтому, основным способом создания обобщенных переменных является использование динамического выделения памяти и присваивание созданной области указателю на обобщение, который описывается следующим синтаксическим правилом:

УказательНаОбобщение = 
    Идентификатор { "," Идентификатор}":" 
         POINTER TO ОбобщающийТип.

Динамическое создание обобщенных переменных предполагает выделение свободной памяти во время выполнения. Доступ к ним осуществляется через указатели. Создание специализаций осуществляется стандартной процедурой NEW, аргументом которой является указатель на обобщение, уточненный допустимым признаком. Ниже показано использование указателя для создания динамической переменной-специализации.

(* Описание указателя в области типов *)
pShape1 = POINTER TO Shape1 
(* Указатель в области описания переменных *)
psh: pShape1
(* Динамическое порождение специализации *)
NEW(psh<rect>);

Специализированные переменные

Задают допустимые специализации. Могут создаваться статически и динамически. Статические специализированные переменные задаются в соответствии с правилом:

СпециализированнаяПеременна¤ = 
   Идентификатор "<" [Признак] ">" 
       { "," Идентификатор "<" [Признак] ">"}":" ОбобщающийТип
   | Идентификатор 
       {","Идентификатор }":" ИбобщающийТип"<" [Признак] ">".

Существует два альтернативных описания. В первом случае признак специализации задается вслед за идентификатором переменной. Это позволяет объявить несколько различных специализаций в одном описании. Во втором варианте, удобном для объявления одинаковых специализаций, их общий признак задается после обобщающего типа. Отсутствие признака, при обязательном наличии угловых скобок, декларирует специализацию по умолчанию.

В соответствии с указанным правилом переменные специализации можно описать следующим образом:

  (* Переменные – специализации *)
  VAR
    (* В одном описании разные специализации *)
    r<MRect.Rectangle>, t<MTrian.Triangle>,
                        c<MCirc.Circle> : Mfig.Figure;
    (* В одном описании одинаковые специализации *)
    r1, r2, r3, r4, r5 : Mfig.Figure<MRect.Rectangle>;

Наряду со статическими переменными допускается их динамическое создание с использованием указателей на специализации. Указатели на специализации описываются следующим правилом:

СпециализированныйУказатель = 
Идентификатор "<" [Признак] ">" 
{ "," Идентификатор "<" [Признак] ">"}":" 
POINTER TO ОбобщающийТип
| Идентификатор 
{"," Идентификатор }":" 
              POINTER TO ОбобщающийТип"<" [Признак] ">".

Динамическое создание специализированных переменных предполагает выделение памяти во время выполнения. Доступ к ним осуществляется через указатели. Создание осуществляется стандартной процедурой NEW, аргументом которой является указатель на специализацию, используемый без уточняющего признака. Ниже показано использование данного приема.

(* Описание указателя в области типов *)
pShape1Rect<rect> = POINTER TO Shape1
(* Указатель в области описания переменных *)
pshr: pShape1
(* Динамическое порождение специализации *)
NEW(pshr);

Допускается присваивание указателей на специализации указателям на соответствующие обобщения:

(* Допустимое присваивание специализации обобщению *)
psh := pshr;

Обобщающие процедуры и обработчики специализаций

Описание обобщающей параметрической процедуры имеет следующий вид:

ОбобщающаяПроцедура = PROCEDURE Имя 
  СписокОбобщающихПараметров [ФормальныеПараметры]
    ((ТелоПроцедуры Имя)  | ":= 0") .

Отличие от обычной процедуры заключается в присутствии списка обобщающих параметров:

СписокОбобщающихПараметров = "{"ГруппаОбобщающих
{";" ГруппаОбобщающих } "}".
ГруппаОбобщающих = [VAR] Идентификатор
 { "," Идентификатор }":" ОбобщающийТип.

Тело содержит обработчик по умолчанию, если для всех обобщающих параметров существует тип по умолчанию. В противном случае оно содержит обработчик исключений. Тело обобщающей процедуры может отсутствовать, что задается "приравниванием" его нулевому значению (по аналогии с чистыми функциями языка программирования C++). В этом случае необходимы обработчики специализаций для различных комбинаций обобщенных параметров.

Обобщающая параметрическая процедура для вычисления периметра любой геометрической фигуры типа Shape1 выглядит следующим образом:

(* Если нужен только общий интерфейс *)
PROCEDURE P {VAR s: Shape1}: REAL := 0

(* Если используетс¤ обработчик по умолчанию *)
PROCEDURE P2 {ps: pShape1}; BEGIN 
  SendExeption(СIncorrect parameterТ) 
END P2

Обработчики обеспечивают реализацию различных комбинаций специализаций, сопоставляемых с обобщениями из списка обобщающих параметров. Комбинация, на которую "настроен" конкретный обработчик, задается значениями признаков в соответствии со следующими синтаксическими правилами:

Обработчик—пециализации = PROCEDURE имя 
  СписокСпециализаций [Формальныеѕараметры] 
    Телоѕроцедуры имя.
СписокСпециализаций = "{" ГруппаСпециализаций
  { ";" ГруппаСпециализаций } "}" .
ГруппаСпециализаций = [VAR] Идентификатор { "," Идентификатор }
  ":" ОбобщающийТип "<" [Признак] ">"
    | [VAR] Идентификатор "<" [Признак] ">"
      { "," Идентификатор "<" [Признак] ">"} ":" 
        ОбобщающийТип.

Каждый элемент списка специализированных параметров должен задавать конкретное значение признака. Специализации должны поэлементно соответствовать параметрам обобщающей процедуры. В обязательном теле процедуры кодируется конкретный метод обработки. Можно использовать один из двух способов задания специализаций, более удобный в рассматриваемом контексте: несколько одинаковых специализаций в группе или несколько разных специализаций в группе. Для представленных выше примеров обобщенных процедур допустимы следующие обработчики, вычисляющие периметры конкретных геометрических фигур:

(* Вычисление периметра прямоугольника *)
PROCEDURE P {VAR r: Shape1<rect>}: INTEGER;
  BEGIN RETURN 2*(r.x + r.y) END P;
(* Вычисление периметра треугольника *)
PROCEDURE P {VAR t<trian>: Shape1}: INTEGER;
  BEGIN RETURN t.a + t.b + t.c END P; 
(* Вычисление периметра ромба *)
PROCEDURE P {VAR t<rhomb>: Shape1}: INTEGER;
  BEGIN RETURN 2*Math.sqrt(t.x*t.x + t.y*t.y) END P;

Использование языковых конструкций

Инструментальная поддержка процедурно-параметрического программирования обеспечивает гибкое расширение разрабатываемых программ. Возникающие при этом возможности можно рассмотреть на примере «хаотической» эволюции простого проекта. Пусть, на начальном этапе разработки программы необходимо создать только прямоугольник и треугольник, реализовать которые можно в двух независимых модулях MRect и MTrian.

(* Модуль, описывающий прямоугольник *)
MODULE MRect;
IMPORT In, Out;
TYPE  
  PRectangle* = POINTER TO Rectangle;
  Rectangle* = RECORD
    x*, y* : INTEGER (* стороны прямоугольника *)
  END;

(* Процедура вывода *)
PROCEDURE Output*(VAR r: Rectangle);
BEGIN
  Out.String("Rectangle: x = "); Out.Int(r.x, 0);
  Out.String(", y = "); Out.Int(r.y, 0);
  Out.Ln;
END Output;
(* Прочие процедуры *)
...
END MRect.

(* Модуль, описывающий треугольник *)
MODULE MTrian;
IMPORT In, Out;
TYPE
  PTriangle* = POINTER TO Triangle;
  Triangle* = RECORD
    a*, b*, c* : INTEGER (* стороны треугольника *)
  END;

(* Процедура вывода *)
PROCEDURE Output*(VAR t: Triangle);
BEGIN
  Out.String("Triangle: a = "); Out.Int(t.a, 0);
  Out.String(", b = "); Out.Int(t.b, 0);
  Out.String(", c = "); Out.Int(t.c, 0);
  Out.Ln;
END Output;
(* Прочие процедуры *)
...
END MTrian.

На следующем этапе в модуле MFig формируются обобщенная геометрическая фигура и процедура ее вывода, использующая вызовы процедур конкретных геометрических фигур.

MODULE MFig;
IMPORT In, Out, MRect, MTrian;
TYPE
  (* Указатель на обобщенную геометрическую фигуру *)
  PFigure* = POINTER TO Figure;
  (* Обобщение геометрической фигуры *)
  Figure* = RECORD CASE TYPE OF   
    MRect.Rectangle | MTrian.Triangle  
  END;

(* Обобщенная параметрическая процедура вывода фигуры *)
PROCEDURE Output* {VAR f: Figure} := 0;
(* Обработчики специализаций *)
(* Вывод обобщенного прямоугольника *)
PROCEDURE Output {VAR r: Figure<MRect.Rectangle>};
  BEGIN MRect.Output(r) END Output;
(* Вывод обобщенного треугольника *)
PROCEDURE Output {VAR t: Figure<MTrian.Triangle>};
  BEGIN  MTrian.Output(t) END Output;
END MFig.

Добавление новых процедур, расширяющих функциональные возможности приложения, осуществляется без изменения уже написанных модулей. При этом можно создавать как обычные, так и любые обобщающие процедуры, в том числе и мультиметоды. Рассмотрим, каким образом осуществляется добавление мультиметода, проверяющего возможность размещения первой фигуры внутри второй. Для этого создадим дополнительный модуль MRTMM, в котором опишем обобщающую процедуру и ее обработчики.

(* Модуль с процедурой проверки того, *)
(* что первая фигура разместится внутри второй *)
MODULE MRTMM;
IMPORT In, Out, MRect, MTrian, MFig;
(* Чистая обобщающая процедура, задающая интерфейс *)
PROCEDURE FirstInSecond* {VAR f1,f2: MFig.Figure}:
BOOLEAN := 0;
(* Обработчики специализаций *)
(* прямоугольник разместится внутри прямоугольника *)
PROCEDURE FirstInSecond 
  {VAR f1,f2: MFig.Figure<MRect.Rectangle>}: BOOLEAN;
BEGIN
  Out.String("Rectangle in Rectangle compare"); Out.Ln;
  RETURN ((f1.x < f2.x) & (f1.y < f2.y)) 
      OR ((f1.x < f2.y) & (f1.y < f2.x))
END FirstInSecond;
(* прямоугольник разместится внутри треугольника *)
PROCEDURE FirstInSecond 
  {VAR f1<MRect.Rectangle>, f2<MTrian.Triangle>: 
    MFig.Figure}: BOOLEAN;
BEGIN ... END FirstInSecond;
(* треугольник разместится внутри прямоугольника *)
PROCEDURE FirstInSecond 
  {VAR f1<MTrian.Triangle>, f2<MRect.Rectangle>: 
    MFig.Figure}: BOOLEAN;
BEGIN ... END FirstInSecond;
(* треугольник разместится внутри треугольника *)
PROCEDURE FirstInSecond 
  {VAR f1, f2: MFig.Figure<MTrian.Triangle>}: BOOLEAN;
BEGIN ... END FirstInSecond;
END MRTMM.

Приведенный пример показывает, что специализации, тип которых известен во время компиляции, обрабатываются так же, как и обычные переменные эквивалентного типа.

Добавление новых геометрических фигур тоже протекает без каких-либо изменений существующих модулей. Например, включим в программу круг, который опишем в отдельном модуле MCirc.

(* Модуль, описывающий круг *)
MODULE MCirc;
IMPORT In, Out;
TYPE
  PCircle* = POINTER TO Circle;
  Circle* = RECORD
    r* : INTEGER (* радиус *)
  END;

(* Процедура вывода *)
PROCEDURE Output*(VAR c: Circle);
BEGIN
  Out.String("Circle: r = "); Out.Int(c.r, 0);
  Out.Ln;
END Output;
(* Прочие процедуры *)
...
END MCirc.

Для расширения уже существующей обобщенной фигуры и обобщающих параметрических процедур можно создать дополнительные модули. Их количество определяется стратегией расширения. Например, для добавления специализации вывода обобщенной фигуры создадим модуль MCircOut.

MODULE MCircOut;
IMPORT In, Out, MFig, MCirc;
TYPE
  (* Расширение обобщения добавлением круга *)
  Mfig.Figure += MCirc.Circle;
(* Вывод обобщенного круга *)
PROCEDURE Mfig.Output {VAR c: Mfig.Figure<MCirc.Circle>};
BEGIN MCircle.Output(c) END Mfig.Output;
END MCircOut.

Проверку их вложенности можно расширить добавлением модуля MRTCMM.

MODULE MRCTMM;
IMPORT In, Out, MRect, MTrian, MFig, MCirc, MRTMM;
TYPE
  (* Расширение обобщения добавлением круга *)
  Mfig.Figure += MCirc.Circle;
(* Дополнительные обработчики специализаций *)
(* прямоугольник разместится внутри круга *)
PROCEDURE MRTMM.FirstInSecond
  {VAR f1<MRect.Rectangle>, f2<MCirc.Circle>: Mfig.Figure}:
BOOLEAN;
BEGIN
  Out.String("Rectangle in Circle compare"); Out.Ln;
  RETURN ((f1.x*f1.x + f1.x*f1.x) < (f2.r*f2.r))
END MRTMM.FirstInSecond;
(* треугольник разместится внутри круга *)
PROCEDURE MRTMM.FirstInSecond
  {VAR f1<MTrian.Triangle>, f2<MCirc.Circle>: Mfig.Figure}: 
BOOLEAN;
BEGIN ... END MRTMM.FirstInSecond;
(* круг разместится внутри прямоугольника *)
PROCEDURE FirstInSecond
  {VAR f1<MCirc.Circle>, f2<MRect.Rectangle>: Mfig.Figure}: 
BOOLEAN;
BEGIN ... END MRTMM.FirstInSecond;
(* круг разместится внутри треугольника *)
PROCEDURE MRTMM.FirstInSecond
  {VAR f1<MCirc.Circle>, f2<MTrian.Triangle>: Mfig.Figure}: 
BOOLEAN;
BEGIN ... END MRTMM.FirstInSecond;
(* круг разместится внутри круга *)
PROCEDURE MRTMM.FirstInSecond
  {VAR f1, f2: Mfig.Figure<MCirc.Circle>}: BOOLEAN;
BEGIN
  Out.String("Circle in Circle compare"); Out.Ln;
  RETURN f1.r < f2.r
END MRTMM.FirstInSecond;
END MRTCMM.

Следует отметить, что описание одного и того же расширения обобщения дублируется в разных модулях. Это необходимо для организации их раздельной компиляции. Окончательная сборка обобщения осуществляется во время компоновки программы. В этот же момент проверяется и непротиворечивость специализаций. Вместо повторного описания расширений можно использовать его однократное определение в промежуточном модуле, импортируемом по мере необходимости. Однако подобный прием ведет к увеличению количества мелких вспомогательных модулей, затрудняющих понимание программы. Это указывает на необходимость дальнейших исследований методов организации программы при использовании процедурно-параметрического программирования.

Заключение

Пусть клиентский модуль MClient осуществляет формирование и обработку обобщенных геометрических фигур.

(* Клиентский модуль, обрабатывающий обобщения *)
MODULE MClient;
IMPORT MRect, MTrian, MCirc, MFig, MRTMM, Console;
TYPE
  (* Расширение обобщения добавлением круга *)
  Mfig.Figure += MCirc.Circle;
VAR
  flag: BOOLEAN;
  (* Переменные – специализации *)
  r<MRect.Rectangle>, 
  t<MTrian.Triangle>, 
  c<MCirc.Circle> : Mfig.Figure;
BEGIN (* Собственно обработка *)
  (* Инициализация переменных *)
  r.x := 3; r.y := 4;
  t.a := 5; t.b := 6; t.c := 7;
  c.r := 10;
  (* Использование обобщенного вывода *)
  Mfig.Output{r}; 
  Mfig.Output{t}; 
  Mfig.Output{c};
  (* Использование мультиметода *)
  flag := MRTMM.FirstInSecond{r, r};
  flag := MRTMM.FirstInSecond{r, t};
  flag := MRTMM.FirstInSecond{r, c};
  flag := MRTMM.FirstInSecond{t, r};
  flag := MRTMM.FirstInSecond{t, t};
  flag := MRTMM.FirstInSecond{t, c};
  flag := MRTMM.FirstInSecond{c, r};
  flag := MRTMM.FirstInSecond{c, t};
  flag := MRTMM.FirstInSecond{c, c};
END MClient.

Модуль MClient импортирует все модули, предоставляющие информацию об используемых программных объектах данных. Это сведения о прямоугольнике, треугольнике, круге, обобщенной фигуре и процедурах осуществляющих ее обработку. Информация об обработчиках специализаций не требуется.

Окончательная структура зависимостей между модулями приведена на рисунке 1. Схема показывает, что даже при бессистемном использовании параметрического полиморфизма возможно гибкое наращивание программы. Еще больше возможностей возникает при планировании эволюционных расширений. Например, не имеет смысла «специализировать» обобщенную геометрическую фигуру прямоугольником и треугольником на первом этапе. Удобнее добавить их в модулях, непосредственно описывающих обработку, как это сделано для круга. Можно разнести по модулям и обработчики специализаций, осуществляющие «выяснение отношений» между различными типами.

Рисунок 1 - Зависимость между модулями программы

Следует также отметить и недостаток подхода, существующий в данной реализации языка. Выделение обобщений и их децентрализованное расширение ведет к появлению большого числа мелких промежуточных модулей. Во многом это связано с модульной структурой языка, которая при рассматриваемом подходе используется "не по назначеию". Недостаток может быть преодолен использованием динамически расширяемых модулей, аналогичных пространствам имен в языке программирования C++. В целом же, инструментальная поддержка процедурно-параметрического подхода позволяет гибко наращивать программу в ходе сопровождения. Многие клиентские модули могут ничего не знать о введении дополнительных специализаций. Реализация параметрической парадигмы в языке, поддерживающем и объектно-ориентированный стиль, позволяет проводить исследования и сравнительный анализ различных методов эволюционного расширения программ. Уже сейчас можно сказать, что изменяются не только стиль кодирования, но и принципы проектирования.

Примечание. Материал для сайта подготовлен на основе статей [11, 12], содержание которых расширено примерами демонстрационных программ.

Используемые источники

  1. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. / Пер. с англ. - СПб: Питер, 2001. - 368 с.

  2. Буч Г. Объектно-ориентированный анализ и проектирование с примерами приложений на C++, 2-е изд. / Пер. с англ. - М.: "Издательства Бином", СПб: "Невский диалект", 1998 г. - 560 с., ил.

  3. Фуксман А.Л. Технологические аспекты создания программных систем. – М.: Статистика, 1979. – 184 с.

  4. Горбунов-Посадов М.М. Расширяемые программы. – М.: Полиптих, 1999.

  5. Легалов А.И. ООП, мультиметоды и пирамидальная эволюция. // Открытые системы - 2002, № 3 (март). С. 41-45.

  6. Легалов А.И. Мультиметоды и парадигмы. // Открытые системы - 2002, № 5 (май). С. 33-37.

  7. Легалов А.И. Процедурно-параметрическая парадигма программирования. Возможна ли альтернатива объектно-ориентированному стилю? - Красноярск: 2000. Деп. рук. № 622-В00 Деп. в ВИНИТИ 13.03.2000. - 43 с.

  8. Легалов А.И. Швец Д.А. Процедурно-параметрические расширения языка программирования Оберон-2. // Вестник Красноярского государственного технического университета. Вып. 23. Математические методы и моделирование. Красноярск, 2001. С. 140-148.

  9. Moessenboeck H., Wirth N. The Programming Language Oberon-2 / Institut fur Computersysteme, ETH Zurich. July 1996.

  10. Вирт Н. Программирование на языке Модула-2. / Пер. с англ. – М.: Мир, 1987. – 244 с.

  11. Легалов А.И. Швец Д.А. Языковая поддержка эволюционного расширения программ. – Доклады СО АН ВШ, №2 (8), июль-декабрь, 2003. С. 102-114.

  12. Легалов А.И. Швец Д.А. Процедурный язык с поддержкой эволюционного проектирования. – Научный вестник НГТУ, № 2 (15), 2003. С. 25-38.