Вступление
И так "пример". Начнем "из далека" с совершенно выдуманной предметной области. Сначала опишем ее (те создадим метамодель), затем нарисуем модель и сгенерируем из нее некий "артефакт": описание в также выдуманном виде. Потом придумаем язык, опишим его, и в конце "натянем" эту предметную область на выдуманный язык, получив на выходе сгенерированные исходники.Заранее прошу прощения, за абсолютную условность предметной области, и за многочисленные грамматические ошибки (с естественными языками у меня как-то не ладится с самого детства :) )
Предметная область
Давайте опишем живые организмы. Скажем что у нас есть сущность Организм, которая состоит из своих частей. Каждая часть это тоже сущность соответствующего типа. Итого имеем два типа сущностей. Что бы не усложнять, мы не вводим ни каких свойств и параметров, кроме двух: имя и описание (тк они есть "с рождения" у любой сущности в нашем инструменте). Запишем это в виде шаблона, который и определит нашу метамодель:: Часть Тела::Class
? часть тела (может соединяться с другой частью тела)
: Организм::Class
? организм (состоит из частей)
> Часть Тела::Class
: Часть Тела::Class::Attribute
c {}
r {%T%f_kind_of("Часть Тела")=false}: {Части тела могут соединяться только между собой}
Перевожу
: X::Y - определяет стереотип Х для класса элементов Y (Class - обычный класс с тз UML), пока что у нас определены два возможных стереотипа классов Часть Тела и Организм.
? - просто описание стереотипа для информации (будет видно в инструменте рисования модели)
> определяет элементы с какими стереотипам могут быть вложены в данный. Тут мы говорим что Части могут быть вложены в Организм.
: X::Y::Z::Y2 - определяет допустимые стереотипы Z вложенных UML-элементов Y2 (атрибутов, операций, связей). В данном случае мы определяем наличие у Частей Тела аттрибутов. Z не указан, те используется стереотип по умолчанию (пустой).
Наконец, последнее что мы видим это констрейнт (две строчки начинающиеся с "c" и "r"), проверяющий что целью этих атрибутов может быть только другая Часть Тела.
Метамодель готова, и можно попробывать нарисовать Модель. Кое какие служебные приготовления типа специального заголвока у шаблона я опускаю, в виду их не интересности. Единственное что нам нужно сделать дополнительно это определить рутовый стереотип папки, в которой мы и будем создавать модель. Как правило эта рутовая папка определяет какой-то конкретный проект.
: Формы Жизни::Category
M
> Организм::Class
М - означет что этот стереотип может быть рутовым, те с него может начинаться Модель
После того как мы откроем новую можель, и свяжем ее с нашим "шаблоном", то получим просто пустой экран, что логично, мы ведь еще ни чего не СОЗДАЛИ. На самом деле пустой экран это верхний уровень нашей модели, в котором мы сейчас можем создать элемент единственного типа (точнее стереотипа) Формы Жизни:
Что мы и сделаем:
Теперь у нас есть пакет с нашим проектом, откроем его, внутри тоже разумеется пустая диаграмма. Аналогично, создадим в нем первый экземпляр Организма который назовем "Собака Шарик":
Теперь нарисуем самого Шарика, для этого внутри класса "Собака Шарик" создадим вложенные классы со стереотипом "Часть тела" (другие нам там и не дадут создать). Не повторяя все ту же последовательность действий, приведу сразу диаграмму:
Мы создали три компоненты "Шарика" и связали их между собой, в соответствии с реальной природой вещей. Теперь нам осталось нарисовать "ноги". Но предположим, что конечности у всех Организмов это довольно специфическая часть тела и для них нам хочется иметь отдельный стереотип, дополним метамодель:
: Нога::Class
= Часть Тела::Class
: Рука::Class
= Часть Тела::Class
= <имя стереотипа> это запись определяющее фактически классическое объектно-ориентирлованное наследование (которое у нас может быть множественным).
Добавим их в список допустимых стереотипов у Организма:
: Организм::Class
? организм (состоит из частей)
> Часть Тела::Class
> Рука::Class
> Нога::Class
Теперь они нам станут доступны в списке возможных стереотипов внутри Организма, и мы можем дорисовать Шарика:
И так, мы описали незамысловатую метамодель предметной области, и нарисовали с ее помощью модель из пары сущностей. Теперь, давайте что-нибудь сгенерируем из нее. Для простоты предположим, что нам нужно сгенерировать описание на псевдо-человеческом языке. Ну что то вроде "Мир состоит из...".
Для этого перво-наперво нам нужно определить ГЕНЕРАТОР. Те по сути конкретный тип получаемых из модели артефактов. Генераторов может быть сколько угодно, например для разных ЯП, документации и тд. Нам пока нужен один:
Generator definitions:
group "All" {
generator "output" {
Description: Описание мира
}
}
output - это идентификатор нашего генератора.
Теперь мы можем в каждом стереотипе из метамодели, задать шаблон для этого генератора, начнем с рутового:
: Формы Жизни::Category
M
> Организм::Class
+ output
C /%SN
O %SN.txt
Мир состоит из: <%CX
>
+output - это начало шаблона для генератора output.
С - определяет путь генерации для этого элемента и всех вложенных
O - задает имя выходного файла. Если генератор стереотипа не определяет выходной файл, то значит его шаблон будет генерироваться в родителя.
%SN - это уже наш чудесный минималистичный синтаксис, (конечно при первом взгляде от него бросает в дрож, но если въехать и привыкнуть то понимаешь что в минимализме есть масса приемуществ)
%S - (Self) доступ к текущем элементу модели, N - вывести имя (Name) элемента (те название элемента на модели).
Таким образом, две команды описанные выше определяют выходной файл с путем: <папка для генерации>/<имя рутового пакета>/<имя рутового пакета>.txt. Дальше идет собственно шаблон, который выводит константную строчу, и запускает генераторы своих детей.
%C - (Child) ребенок текущего элемента
<... > - это цикл, в данном случае по %C - те по детям. X - запускает текущий генератор на элементе.
Теперь опишем шаблон для Организмов, те для "детей":
: Организм::Class
+ output
%SN[, %SD,] это <{; }%CX>.
%SN - имя Организма
%SD - (Documentation) документация элемента с модели
[...] - условие, в данном случая его особая "пустая" форма, означает что запятые и пробел выведутся, только в том случае если у элемента не пустая документация.
Ну и в конце опять цикл по детям Организма. В фигурных скобках внутри цикла параметр, определяющий строку разделитель элементов.
Наконец последний шаблон, для Части тела:
: Часть Тела::Class
+ output
%SN[ %SD][, из которой растут <{, }[%CN ]%C%TN>]
Аналогично, выводим имя и документацию, после чего если часть тела связана с другой то выводим имя связи (атрибута) и его тип, те имя той части с на которую он указывает.
%T - это "таргет" (Target) элемента, например тип атрибута или тип возвращаемого значение операции, тип связи и тд. Соответственно, связка %C%T - это доступ к таргету текущего ребенка.
Теперь мы можем запустить генерацию:
Выходной файл, как можно уже догадаться, будет следующим:
Мир состоит из: Собака Шарик это Голова зубастая; Хвост рогаликом; Тушка пятнистая, из которой растут Голова, Передняя правая Лапа, Передняя левая Лапа, Задняя левая Лапа, Задняя правая Лапа, Хвост.
Если мы расширим модель, то и описание разумеется будет расширяться. Предположим мы хотим теперь описать новую форму жизни - Человек. И предположим, что мы понимаем что Людей у нас в модели будет много (в отличие от Шарика, который один), рисовать каждый раз человека из чего он состоит, точно не хочется, поэтому мы используем summoning те порождение элементов модели. Для начала определим новый стереотип Человек (и сразу за компанию еще один - Бог, что за человек без Бога ;)) на метамодели:
: Человек::Class
= Организм::Class
: Бог::Class
= Человек::Class
Теперь, как мы условились выше нам нужно один раз описать из чего состоит ЛЮБОЙ человек и Бог, что бы не делать это каждый раз ручками на модели:
: Человек::Class
= Организм::Class
+ spell
%S%f_add_class(HEAD,Часть Тела,Голова,TMP)
%S%{HEAD}%f_set_documentation(светлая)
%S%f_add_class(L_HAND,Рука,Левая рука,TMP)
%S%f_add_class(R_HAND,Рука,Правая рука,TMP)
%S%f_add_class(L_FOOT,Рука,Левая нога,TMP)
%S%f_add_class(R_FOOT,Рука,Правая нога,TMP)
%{TMP}%f_set_documentation(немного короче левой)
: Бог::Class
= Человек::Class
+ spell
%S%f_add_class(SECOND_HEAD,Часть Тела,Вторая голова,TMP)
%{TMP}%f_set_documentation(еще лучше первой)
= Человек::Class;spell
+ spell - идентификатор специального системного генератора для суммонинга.
%S%f_add_class(X,Y,Z,V) - вызов функции которая добавит внутрь текущего элемента (%S) новый класс указанного стереотипа.
Y - стереотип создаваемого класса
Z - имя создаваемого класса
X - уникальный идентификатор и имя переменной внутри текущего элемента с которым будет связан созданный элемент. V - почти тоже самое только для глобального пространства. Эти переменный используются в дальнейшем для доступа к созданным элементам.
%S%{HEAD} - доуступ к переменной HEAD (созданной шагом выше) на текущем элементе. Эквивалентная запись в данном случае это просто %{TMP} (такая двойственность возникла исторически, но иногда по прежнему удобна)
%f_set_documentation - устанавливает поле документации
И так мы "ссумонили" типичного человека и Бога. Теперь мы можем использовать уже готовый стереотип без ручного рисования на модели, сохранив при этом возможность уточнить "состав" конкретного человека, если понадобиться, добавив вручную то что нам нужно.
Дорисуем модель:
После генерации предсказуемо получим на выходе:
Мир состоит из: Собака Шарик это Голова зубастая; Хвост рогаликом; Тушка пятнистая, из которой растут Голова, Передняя правая Лапа, Передняя левая Лапа, Задняя левая Лапа, Задняя правая Лапа, Хвост.
Вася это Голова светлая; Левая рука; Правая рука; Левая нога; Правая нога немного короче левой.
Будда это Вторая голова еще лучше первой; Голова светлая; Левая рука; Правая рука; Левая нога; Правая нога немного короче левой.
Маша это Голова светлая; Левая рука; Правая рука; Левая нога; Правая нога немного короче левой.
Разумеется, такая "упаковка" одних стереотипов в другие может быть любой вложенности и сложности. Мы можем точно так же определить стереотип... ну не знаю... Семья, и ссумонить ее состав автоматом, затем стереотип Деревня состоящий из 50 семей и тд. Возможности самого языка достаточно широки для того что бы код суммонинга (ровно как и обычного генерирующего шаблона) мог быть практически произвольной структуры, с условиями, циклами, кейсами, наследованием и полиморфизмом и был способен выразить любые правила и алгоритмы.
Теперь еще немного расширим метамодель. Определим на ней возможность связи между Организмами:
: Организм::Class::ClassDependency
: Организм::Class::любит::ClassDependency
= Организм::Class::ClassDependency
: Организм::Class::верит в::ClassDependency
= Организм::Class::ClassDependency
- мы определили абстрактную связь и две конкретные "любит" и " верит". Теперь они доступны нам на модели, используем их:
Подправим шаблон нашего генератора:
: Организм::Class
+ output
%SN[, %SD,] это <{; }%CX>.<{}{%CC=Dependency} %SN %CS %C%TN.>
теперь мы дополнительно выводим описание установленных связей.
<..> - Цикл по детям с условием выборки только детей с типом класса (%CС - Class) Dependency ("связь"), c выводом своего имени, их стереотипа (%СS - Stereotype) и имени цели.
После генерации получим изменения:
Мир состоит из: Собака Шарик это Голова зубастая; Хвост рогаликом; Тушка пятнистая, из которой растут Голова, Передняя правая Лапа, Передняя левая Лапа, Задняя левая Лапа, Задняя правая Лапа, Хвост. Собака Шарик верит в Вася.
Вася это Голова светлая; Левая рука; Правая рука; Левая нога; Правая нога немного короче левой. Вася любит Собака Шарик. Вася любит Маша. Вася верит в Будда.
Будда это Вторая голова еще лучше первой; Голова светлая; Левая рука; Правая рука; Левая нога; Правая нога немного короче левой.
Маша это Голова светлая; Левая рука; Правая рука; Левая нога; Правая нога немного короче левой.
Язык Программирования
Мы описали предметную область, нарисовали модель и сгенерировали ее описание. Предположим теперь что нам нужно получить по имеющейся модели код на "нашем" яп, который бы являлся ее отражением. Для начала определим метамодель языка, а затем проведем трансформацию (с помощью суммонинга) предметной области в язык.
Пусть наш ЯП обладает минимальным набором понятий (это ж пример всетаки, да, не беру реальный, потому что Дельфи и java давным-давно забыл, С++ уже начинаю, а Objective C боюсь многих своим синтаксисом с ума сведет), пусть будет что-то вроде такого джава образного, что бы все в одном файле:
module M1 {
class C1 ---> BaseClass {
property propA: TypeA;
operation opB (arg1: Type1, arg2: Type2): TypeRet {
// тело метода
}
}
}
Тогда нам для примера в метамодели языка (назовем его язык Х) нужны будут понятия модуля, класса, проперти и операции, ну пусть еще, конструктора.
Запишем это на шаблоне. Для начала дополним описание генераторов, новым для нашего языка:
Generator definitions:
group "All" {
generator "output" {
Description: Описание мира
}
generator "langx" {
Description: А вот и код
}
}
Теперь добавим новые стереотипы, с пустым генератором языка:
: ModuleX::Category
> ClassX::Class
+ langx
O src/%SN.langx
: ClassX::Class
d
+ langx
: ClassX::Class::property::Attribute
+ langx
: ClassX::Class::Operation
a ra
m t
+ langx
: ClassX::Class::ctor::Operation
= ClassX::Class::Operation
T
+ langx
И так сначала идет определения для модуля. МодульХ может содержать элементы КлассовХ и будет порождать файл в подкаталоге src с названием <имя модуля>.langx.
КлассуХ указываем (d) что этот элемент должен уметь работать с derived свойствами (операциями и/или атрибутами), те мета-модель будет позволять такому элементу реализовывать абстрактных детей своего предка и/или перекрывать их (разумеется какой конкретный смысл вкладывать в эти понятия зависит исключительно от шаблона, но в данном случае мы имеем ввиду именно классичсекие наследование, реализацию и перекрытие).
Определяем на КлассеХ проперти, операцию без стереотипа, и конструктор, как операцию со стереотипом ctor. Операции могут быть (r) регулрными (regular) или (a) абстрактными (abstract), и быть доступными для реализации и перекрытия в наследниках (m t). Конструкторы, кроме того могут не иметь таргета (T), те им необязательно задавать тип возвращаемого результата.
Мы уже можем сформировать модель на нашем языке, но естественно из нее ни чего кроме пустого файла .langx не сгенерируется. Поэтому следующим шагом определим шаблон генерации.
Представив себе конечный результат который мы хотим получить, мы можем сразу выделить некоторые закономерности и общности в генерации разных элементов. В нашем выдуманном пример их будет минимальное кол-во, а в реальности их может быть очень много. Допусти мы решим, что все элементы имеют документацию которая должна выводиться единообразно, тогда мы можем сразу определить общие части шаблона для всех элементов, конкретизируя специфику для каждого стереотипа отдельно. Для этого добавим еще один абстрактный стереотип LangX и отнаследуем от него все остальные:
: LangX::Class
+ langx
// %SD
%S%f_element_definition()
%f _element_definition
/ пусто
: ClassX::Class
= LangX::Class
: ClassX::Class::Operation
= LangX::Class
...
LangX являясь базовым стереотипом определяет общий шаблон, который выводит документацию в нужном нам формате (в данном случае просто предваренную символами комментария), и затем вызывает метод стереотипа _element_definition который в базовой реализации ни чего не делает.
Определим этот метод для реальных стереотипов:
: ClassX::Class
%f _element_definition
class %SN[ --\> <{, }%GN>] {
<%CX
>\
<%oX
>};
Шаблон для Класса, согласно нашему синтаксису выводит его имя, указание базового класса (%G - Generalization), если есть, затем разворачивает генерацию своих детей, и абстрактных детей унаследованных от предков (%o), которые данный класс реализует.
: ClassX::Class::Operation
%f _element_definition
operation %f_to_javaex(%SN) (<{, }%CN:%C%TN>): %TN {
}
Операция выводится всегда с маленькой буквы слитно (в стиле джавы), за это отвечает глобальна функция преобразование строки _to_javaex. Все остальное согласно определенному выше синтаксису. Тело операции пока не выводим.
: ClassX::Class::property::Attribute
%f _element_definition
property %f_to_javaex(%SN): %TN;
С проперти все аналогично и даже проще.
: ModuleX::Category
%f _element_definition
module %SN {
<{\n\n}%CX>
}
Ну и на конец, модуль выводит свой заголовок и разворачивает всех своих детей, аналогично классу.
Мы забыли про конструктор, который в отличие от обычной операции всегда имеет определенный тип результата, выразим это в шаблоне, используя суммонинг:
: ClassX::Class::ctor::Operation
= ClassX::Class::Operation
T
+ spell
%S%f_set_target(%PU)
Те мы неявно устанавливаем любому конструктору его таргет указывая класс своего родителя (%P - Parent, U - гуид элемента, в принципе тут можно использовать и имя, но имя может быть не однозначным, а гуид гарантированно указывает на нужный элемент).
Теперь вернемся к нашей предметной области и ее модели. Допустим мы хотим отобразить ее на наш язык по следующим простым правилам: каждый экземпляр сущности из предметной области должен соответствовать программному классу, составные части организма должны быть доступны как его проперти. Для каждого организма должен быть специальный конструктор принимающий экземпляры других организмов с которыми у текущего есть какая-либо связь.
В реальной жизни, любое такое отображение как правило будет опираться на какой-либо фреймворк, как минимум экземпляры классов отображения скорее всего будут наследниками базовых классов из этого фреймворка. Поэтому для нашего примера мы тоже "придумаем" примитивный фреймворк и используем его в отображении, сказав что все Части Тела должны наследоваться от базового класса BodyPartsBase, а все организмы от OrganismBase. Плюс каждый организм должен дополнительно реализовать пустой дефолтный конструктор из базового класса.
Для начала создадим наш фреймворк. Формально он может находиться за границами модели, в этом случае для его использования нужно будет завести как минимум заглушки для тех элементов фреймворка, которые мы будем использовать в шаблонах, что бы можно было на них ссылаться. Или же мы можем его нарисовать как часть нашей модели или экспортировать из другой уже имеющейся. Но в нашем примере мы пойдем третьим путем - мы его ссумоним. Формально это тоже самое что и нарисовать его, но с тз примера это более интересный путь, а иногда и более простой. Для этого определим новую функцию _summon_framework и вызовем ее в spell проекта:
: Формы Жизни::Category
+ spell
%S%f_summon_framework()
<%CX>
%f _summon_framework
%S%f_add_category(XWF,ModuleX,XFramework,FW)
%{FW}%f_set_documentation(Фреймворк для Форм Жизни)
%{FW}%f_add_class(BP_BASE,ClassX,BodyPartsBase,ADDS)
%{ADDS}%f_set_documentation(Базовый класс для Частей тела)
%{FW}%f_add_class(ORG_BASE,ClassX,OrganismBase,ADDS)
%{ADDS}%f_set_documentation(Базовый класс для Организмов)
%{ADDS}%f_add_operation(CTOR_DEF,ctor,make_deafult():OrganismBase,TMP)
%{TMP}%f_set_documentation(Дефолтный пустой конструктор - должен определятся в конкретном экземпляре)
%{TMP}%f_set_abstraction_type(abstract)
%{ADDS}%f_add_operation(EX1,,all_my_parts():BodyPartsBase,TMP)
%{TMP}%f_set_documentation(Операция для примера)
В самой функции, мы создадим модуль для фреймворка, и два класса - базовый для частей тела и базовый для организма. Добавим на OrganismBase дефолтный пустой конструктор, и скажем ему что он абстрактный. А так же общую операцию all_my_parts (просто для примера). В общем случае сложность такого фреймворка может быть произвольной. Запустим генерацию нашей модели и получим на выходе один новый файл с нашим фреймворком:
Его содержимое:
// Фреймворк для Форм Жизни
module XFramework {
// Базовый класс для Частей тела
class BodyPartsBase {
};
// Базовый класс для Организмов
class OrganismBase {
// Дефолтный пустой конструктор - должен определятся в конкретном экземпляре
operation makeDeafult (): OrganismBase {
}
// Операция для примера
operation allMyParts (): BodyPartsBase {
}
};
}
Все хорошо кроме отсутствия тела функции, и того что абстрактный конструктор вообще говоря должен этого тела не иметь вовсе. Допишем шаблон, так что бы абстрактные методы не имели тела, а заканчивались указанием "= 0;", а для остальных включая реализацию абстрактных тело таки появлялось:
: ClassX::Class
%f _element_definition
%f_set_var(CONTEXT,S)\
class %SN[ --\> <{, }%GN>] {
...
: ClassX::Class::Operation
%f _element_definition
operation %f_to_javaex(%SN) (<{, }%CN:%C%TN>): %TN\
[{%{CONTEXT}U=%PU&%Sa=abstract}{%S%f_body()} = 0;]
%f _body
{
%U[
]
}
В шаблоне класса, мы сначала определяем текущий контекст те тот конкретный экземпляр класса чье содержимое мы сейчас генерируем. Затем в шаблоне для метода мы проверяем в каком контексте мы работаем и если это абстрактный метод и контекст это наш непосредственный родитель (те класс на котором и определен абстрактный метод) то выводим указание " = 0;", иначе вызываем метод с шаблоном для тела, который определяем ниже.
%U - это вывод так называемых Юзер Секций (User Sections) - кусков текста обрамленных специального вида комментариями (формат их может настраиваться для каждого генератора отдельно), которые при повторной генерации парсятся генератором, их содержимое идентифицируется и вставляется во вновь сгенерированный код в место с теми же самыми идентификаторами. За счет этого становится возможным, дополнять сгенерированные артефакты любыми пользовательскими вставками, которые будут сохранены при перегенерации, перемещены в другое место если это потребуется, или удалены если был удален элемент с которым они были связаны.
Запустим генерацию:
// Фреймворк для Форм Жизни
module XFramework {
// Базовый класс для Частей тела
class BodyPartsBase {
};
// Базовый класс для Организмов
class OrganismBase {
// Дефолтный пустой конструктор - должен определятся в конкретном экземпляре
operation makeDeafult (): OrganismBase = 0;
// Операция для примера
operation allMyParts (): BodyPartsBase {
//#UC START# *D5AD8599E380*
//#UC END# *D5AD8599E380*
}
};
}
//#UC START# *D5AD8599E380* - это начало юзерсекции с ИД D5AD8599E380, соответствующим ИД элемента операции all_my_parts, который автоматически получается при суммонинге и сохраняется в файле модели. //#UC END# - соответственно ее конец.
Все что внутри нее будет сохранено при перегенерации, поэтому мы можем спокойно написать реализацию этого метода.
Будем считать что фреймворк готов, и можно приступать к отображение предметной области. Первая проблема с которой мы сталкиваемся это имена элементов. В нашем примере они русские, а для отображения на langX нужны латинские. Для ее решения мы добавим на все элементы предметной области дополнительное поле-свойство, в котором проектировщик сможет указать латинский аналог русского названия элемента. Что бы не добавлять свойтсво на каждый из элементов предметной области, мы добавим один базовый стереотип, на котором его и определим:
: ФЖ Элемент::Class
p eng_name:s ? англ название
%f _x_name
[{}{%SN}%S{eng_name}]
: Часть Тела::Class
= ФЖ Элемент::Class
%f _x_name
%S%[inherited]Of%P%f_x_name()
%f _x_name
%S%[inherited]Of%P%f_x_name()
...
: Организм::Class
= ФЖ Элемент::Class
...
p <имя свойства>:<тип>[ ? <описание>] - задает пользовательское свойство для стереотипа. Свойства могут быть бинарными (да/нет), строковыми (произвольная строка), выбором из списка значений или файлом. В данном случае это просто строка.
Функция _x_name введена просто для удобства - если не задано английское имя, то берется основное имя элемента. А для Части тела она еще использует имя родителя, что бы получить уникальное имя класса.
Добавим английские имена всем элементам:
Функция _x_name введена просто для удобства - если не задано английское имя, то берется основное имя элемента. А для Части тела она еще использует имя родителя, что бы получить уникальное имя класса.
Добавим английские имена всем элементам:
Теперь реализуем собственно отображение. Тут есть два варианта как поступить. Первый вариант это отнаследовать стереотипы предметной области от стереотипов языкаХ, и дописать нужную специализацию. Второй, более правильный, это явно ссумонить все что нужно, мы выберем этот вариант. Определим спелы (spell) для наших элементов:
: Формы Жизни::Category
+ spell
%S%f_summon_framework()
%S%f_add_category(X_IMPL,ModuleX,%S{eng name},TMP)
%{TMP}%f_add_dependency(%{FW}U)
<%CX>
<%C#f_spell_usage()>
После того как наш рутовый пакет, создаст фреймворк, мы добавим модуль в котором и будет находиться отображение. В качесте имени моуля мы используем значение пользовательского свойства {eng name}. Укажем что отображение зависит от фреймворка (это необходимо для контроля доступа элементов друг к другу), после чего вызовим суммонинг своих детей. Поскольку у нас в метамодели предметной области допускаются циклические зависимости (Вася любит Шарика, а Шарик верит в Васю), то отображение мы разобьем на две фазы, первая ссобственно суммонинг необходимых классов, а вторая связывание их друг с другом, в противном случае возможны неоднозначности, когда нам нужно использовать элемент который мы еще не ссумонили. Для этого мы добавим метод _spell_usage, который определим на только Организме (форма вызова метода через # означает что метод будет вызван только если он определен на экземпляре, в случае с % попытка вызвать не определенный метод приведет к ошибке)
: Организм::Class
+ spell
%P%{X_IMPL}%f_add_class(X_%SU,ClassX,%S%f_x_name(),ADDS)
%{ADDS}%f_add_inheritable(OrganismBase)
%{ADDS}%f_set_documentation(Класс реализации для %SN[ %SD])
%S%f_set_var(X_IMPL,{ADDS})
<%CX>
%f _spell_usage
%f_set_var(MY_IMPL,S%{X_IMPL})
%f_clear_list(CTOR_ARGS)
<
[{%CC=Dependency}{
%{MY_IMPL}%f_add_attribute(X_%CU,property,%C{eng_name}:%C%{X_IMPL}N,TMP)
%{TMP}%f_set_documentation(%CN[ %CD])
}
%f_add_to_list(CTOR_ARGS,C)
]
>
[{%{CTOR_ARGS}<{}{}{%CC}>!=0}
%{MY_IMPL}%f_add_operation(X_CTOR,ctor,make_with(%{CTOR_ARGS}<{,}%C{eng_name}:%C%T%{X_IMPL}N>),TMP)
%{TMP}%f_set_documentation(Конструктор. Параметры: %{CTOR_ARGS}<{; }%C{eng_name}:%C%TN[ %C%TD]>)
]
Организм добавляет в модуль, созданный на родителе, свой класс отображения, указывает ему от кого он должен наследоваться, определяет документацию, и сохраняет ссылку на него у себя в переменной X_IMPL. Во второй фазе отображения (в методе _spell_usage), мы обходим все своих детей, для Частей тела добавляем соответствующее проперти, а для связей, набираем их список, на основе которого затем формируем конструктор.
: Часть Тела::Class
+ spell
%P%P%{X_IMPL}%f_add_class(X_%SU,ClassX,%S%f_x_name(),ADD)
%{ADD}%f_add_inheritable(BodyPartsBase)
%{ADD}%f_set_documentation(Класс реализации для %SN[ %SD] от %PN[ %PD])
%S%f_set_var(X_IMPL,{ADD})
Наконец, Часть тела просто добавляет свой класс отображения с указанием предка, и все.
Кажется, все готово и можно запустить генерацию, на выходе мы получаем один новый файл с описание отображения:
module Example {
// Класс реализации для Голова зубастая от Собака Шарик
class HeadOfSharickTheDog --> BodyPartsBase {
};
// Класс реализации для Лапа от Собака Шарик
class PawOfSharickTheDog --> BodyPartsBase {
};
// Класс реализации для Хвост рогаликом от Собака Шарик
class TailOfSharickTheDog --> BodyPartsBase {
};
// Класс реализации для Тушка пятнистая от Собака Шарик
class BodyOfSharickTheDog --> BodyPartsBase {
};
// Класс реализации для Голова светлая от Вася
class HeadOfVasija --> BodyPartsBase {
};
// Класс реализации для Левая рука от Вася
class LeftHandOfVasija --> BodyPartsBase {
};
// Класс реализации для Правая рука от Вася
class RightHandOfVasija --> BodyPartsBase {
};
// Класс реализации для Левая нога от Вася
class LeftFootOfVasija --> BodyPartsBase {
};
// Класс реализации для Правая нога немного короче левой от Вася
class RightFootOfVasija --> BodyPartsBase {
};
// Класс реализации для Вторая голова еще лучше первой от Будда
class SecondHeadOfBuddah --> BodyPartsBase {
};
// Класс реализации для Голова светлая от Будда
class HeadOfBuddah --> BodyPartsBase {
};
// Класс реализации для Левая рука от Будда
class LeftHandOfBuddah --> BodyPartsBase {
};
// Класс реализации для Правая рука от Будда
class RightHandOfBuddah --> BodyPartsBase {
};
// Класс реализации для Левая нога от Будда
class LeftFootOfBuddah --> BodyPartsBase {
};
// Класс реализации для Правая нога немного короче левой от Будда
class RightFootOfBuddah --> BodyPartsBase {
};
// Класс реализации для Будда
class Buddah --> OrganismBase {
// Вторая голова еще лучше первой
property secondHead: SecondHeadOfBuddah;
// Голова светлая
property head: HeadOfBuddah;
// Левая рука
property leftHand: LeftHandOfBuddah;
// Правая рука
property rightHand: RightHandOfBuddah;
// Левая нога
property leftFoot: LeftFootOfBuddah;
// Правая нога немного короче левой
property rightFoot: RightFootOfBuddah;
// Дефолтный пустой конструктор - должен определятся в конкретном экземпляре
operation makeDeafult (): OrganismBase {
//#UC START# *F66E89A23C1C_BASE*
//#UC END# *F66E89A23C1C_BASE*
}
};
// Класс реализации для Голова светлая от Маша
class HeadOfMary --> BodyPartsBase {
};
// Класс реализации для Левая рука от Маша
class LeftHandOfMary --> BodyPartsBase {
};
// Класс реализации для Правая рука от Маша
class RightHandOfMary --> BodyPartsBase {
};
// Класс реализации для Левая нога от Маша
class LeftFootOfMary --> BodyPartsBase {
};
// Класс реализации для Правая нога немного короче левой от Маша
class RightFootOfMary --> BodyPartsBase {
};
// Класс реализации для Маша
class Mary --> OrganismBase {
// Голова светлая
property head: HeadOfMary;
// Левая рука
property leftHand: LeftHandOfMary;
// Правая рука
property rightHand: RightHandOfMary;
// Левая нога
property leftFoot: LeftFootOfMary;
// Правая нога немного короче левой
property rightFoot: RightFootOfMary;
// Дефолтный пустой конструктор - должен определятся в конкретном экземпляре
operation makeDeafult (): OrganismBase {
//#UC START# *F66E89A23C1C*
//#UC END# *F66E89A23C1C*
}
};
// Класс реализации для Собака Шарик
class SharickTheDog --> OrganismBase {
// Голова зубастая
property head: HeadOfSharickTheDog;
// Лапа
property paw: PawOfSharickTheDog;
// Хвост рогаликом
property tail: TailOfSharickTheDog;
// Тушка пятнистая
property body: BodyOfSharickTheDog;
// Конструктор. Параметры: beleveTo:Вася
operation makeWith (beleveTo:Vasija): SharickTheDog {
//#UC START# *3565C7C4C308*
//#UC END# *3565C7C4C308*
}
// Дефолтный пустой конструктор - должен определятся в конкретном экземпляре
operation makeDeafult (): OrganismBase {
//#UC START# *F66E89A23C1C*
//#UC END# *F66E89A23C1C*
}
};
// Класс реализации для Вася
class Vasija --> OrganismBase {
// Голова светлая
property head: HeadOfVasija;
// Левая рука
property leftHand: LeftHandOfVasija;
// Правая рука
property rightHand: RightHandOfVasija;
// Левая нога
property leftFoot: LeftFootOfVasija;
// Правая нога немного короче левой
property rightFoot: RightFootOfVasija;
// Конструктор. Параметры: loveTo:Собака Шарик; loveTo:Маша; beleveTo:Будда
operation makeWith (loveTo:SharickTheDog, loveTo:Mary, beleveTo:Buddah): Vasija {
//#UC START# *5558B8CAB716*
//#UC END# *5558B8CAB716*
}
// Дефолтный пустой конструктор - должен определятся в конкретном экземпляре
operation makeDeafult (): OrganismBase {
//#UC START# *F66E89A23C1C*
//#UC END# *F66E89A23C1C*
}
};
}
При внимательном изучении сгенерированного кода отображения мы можем увидим несколько недостатков. Первый это то что нам нужно подключить модуль с фреймворком. Второй, что мы хотим что бы специальный конструктор makeWith вызывал дефолтный. Наконец, третий, то что ИД юзер секций для реализаций абстрактного базового конструктора сгенерировались одинаковыми, что очевидно приведет к ошибке при повторной генерации, тк будет н понятно какая секция к чему относится.
Для подключения фреймворка добавим в шаблон модуля следующий код:
: ModuleX::Category
%f _element_definition
[import <{,} %DN>;
]module %SN {
<{\n\n}%CX>
}
этот шаблон выведет через запятую все другие модули от которые непосредственно зависит текущий.
Для вызова базового конструктора, добавим такой шаблон:
: ClassX::Class::ctor::Operation
+ spell
%S%f_set_target(%PU)
%S%f_set_pre_uc_content(langx,,%S%f_ctor_pre_content())
%f _ctor_pre_content
self = %P%GN.makeDefault;
_set_pre_uc_content - это системная функция которая добавляет указанный в аргументе шаблон ПЕРЕД выводом юзер секции.
Наконец, для устранения дублирующихся ИД юзер секций, используем уже имеющийся у нас контекст генерации:
: ClassX::Class::Operation
%f _body
{
[{%{CONTEXT}U=%PU}{\
%U[{_BASE_FOR_%{CONTEXT}U}
]
}\
%U[
]
]\
}
тут мы проверяем кто именно сейчас генерирует метод, и если это не его не посредственный родитель, до расширяем ИД юзер секции, делая его уникальным.
Теперь после перегенерации все проблемы будут устранены:
import XFramework;
module Example {
....
// Конструктор. Параметры: beleveTo:Вася
operation makeWith (beleveTo:Vasija): SharickTheDog {
self = OrganismBase.makeDefault;
//#UC START# *3565C7C4C308*
//#UC END# *3565C7C4C308*
}
// Дефолтный пустой конструктор - должен определятся в конкретном экземпляре
operation makeDeafult (): OrganismBase {
//#UC START# *F66E89A23C1C_BASE_FOR_5ECD455013CA*
//#UC END# *F66E89A23C1C_BASE_FOR_5ECD455013CA*
}
....
}
Заключение
Собственно на этом данный пример я заканчиваю. Надеюсь он помог понять основные принципы работы. В заключение добавлю, что даже в рамках этого придуманного примера, мы можем и дальше показать на сколько гибкий инструмент у нас в руках, например можно добавить те же настройки о которых упоминали ранее, или сделать сущности предметной области персистентными, добавить поддержку отображения в другие языки, показать как можно расширить существующие языки добавив им новые полезные свойства и тд.
Дополнение
По просьбе трудящихся добавляю мета-модель:
И ключевую ЧАСТЬ мета-мета-модели. Повторю она ЕДИНАЯ для ВСЕХ:
Самый интересный тут "квадартик" это MDAClass - оно определяет сам себя. И сам из себя генерируется.
Дополнение
По просьбе трудящихся добавляю мета-модель:
И ключевую ЧАСТЬ мета-мета-модели. Повторю она ЕДИНАЯ для ВСЕХ:
Самый интересный тут "квадартик" это MDAClass - оно определяет сам себя. И сам из себя генерируется.
Макс, диаграмму самой мета-модели было бы здорово привести. А не только шаблоны.
ОтветитьУдалитьТекст интересный и содержательный. Благодарю.
ОтветитьУдалитьСпасибо, надеюсь, он помог. С удовольствием отвечу на вопросы, если возникнут.
Удалить«Спасибо, надеюсь, он помог. С удовольствием отвечу на вопросы, если возникнут.»
Удалить-- Да, вопросы, вне всякого сомнения, возникнут.
Только хотелось бы их в "личку" задать. У Вас есть почтовый адрес, по которому с Вами можно было бы связаться напрямую?
Просто не хотелось бы некоторыми вопросами здесь "волну" поднять - я же не самоутверждаться сюда пришёл. А "волны" иногда поднимаются иногда совершенно без моей воли на то...
Ну я бы предпочел обсуждение вести тут (все таки не исключено что оно будет полезно не только нам с Вами), а про волны... ды Бог с ними :) предлагаю не обращать на волны внимания, ну а коллег стараться их не поднимать... :)
УдалитьOk. Мои комментарии ниже.
Удалить> Макс, диаграмму самой мета-модели было бы здорово привести. А не только шаблоны.
ОтветитьУдалитьда, я думал, но чет уже сил не хватило :) Но если коллеги попросят, то сделаю, но имхо, пока это только запутать может. Потом еще и мета-мета-модели захочется, которая сама себя генерит... это уже потом если интересно будет кому-то
«Макс, диаграмму самой мета-модели было бы здорово привести. А не только шаблоны.»
Удалить-- Диаграмма вашей мета-модели могла бы быть полезной. Спасибо за шаблоны, без них диаграмма не давала бы полного представления. Да и сейчас, восстановить то, что было на диаграмме мета-модели можно вполне, но IMHO с диаграммой было бы лучше.
«Потом еще и мета-мета-модели захочется, которая сама себя генерит... это уже потом если интересно будет кому-то»
-- Да, это тоже интересно. А если бы диаграмма мета-мета-модели была бы построена для описания чего-то конкретного, то можно было бы сделать заключение о круге вопросов, выносимых на её уровень.
Добавил в конец и то и другое :)
УдалитьСтранно почему-то пред-пред-идущий коментарий не добавился:
Удалить> А если бы диаграмма мета-мета-модели была бы построена для описания чего-то конкретного,
дык мета-мета-модель она ЕДИНА для всех мета-моделей. На ней у нас все и построена. Метамета-можель описывает как раз те кирпичики те примитивы нашего шаблона из которых и формируются прикладные мета-можели
«дык мета-мета-модель она ЕДИНА для всех мета-моделей. На ней у нас все и построена. Метамета-можель описывает как раз те кирпичики те примитивы нашего шаблона из которых и формируются прикладные мета-можели»
Удалить-- Хотелось бы получить представление о типаже примитивов, имеющих столь всеобъемлющее значение, что Вы с коллегами сочли необходимым вынести их на уровень мета-мета-модели.
Это может пригодиться для осознания методики вынесения примитивов на уровень обобщений.
Вероятно, этот процесс протекает вполне естественно, в процессе анализа предметной области (или даже, предметных областей) выделяются общие правила, которые едины для всех. Вот и хочется получить представление о типаже таких правил.
Не имея возможности применить вашу технологию на практике я могу только задавать вопросы.
Этот комментарий был удален автором.
УдалитьНу вот диаграмму ключевых элементов мета-мета-можели я привел выше (добавил к тексту пример), тпаж примитивов это класс стереотипа, категория стереотипов, различные связи между стереотипами, операции и атрибуты (например те самые пользовательские свойства). И тд, например элементы машины состояний или сиквенсов.
УдалитьНа основе этих примитивов, уже определяется какая-то конкретная метамодель, например та что в моем примере (еее диаграмму я тоже добавил в текст пример).
Вот например если смотреть сверху в низ: Есть на модели из примера элемент Человек, он имеет СТЕРЕОТИП "Организм" этот стереотип определен нашей мета-моделью. На мета-модели он выглядит как элемент Организм со стереотипом MDAClass (предпоследняя диаграмма) - это класс стереотипов. MDAClass - определен на мета-мета-модели как элемент MDAClass со стереотипом... тоже MDAClass... те он определяет сам себя, вот в это не сразу можно въехать, но реально это и не нужно для понимания всего остального. Главное что нужно понять, что мета-мета модель ни какого отношения ни к одной из предметных областей не имеет. ВООБЩЕ. Она лишь описыват то с помощью чего потом мы описываем предметную область.
Не в плане замечаний или критики, но мне казалось, что поскольку материал, который Вы взялись освещать, безусловно, представляет значительный интерес, было бы неплохо сначала (или параллельно) обозначить, какие проблемы решаются посредством технологии, которую Вы с ним описываете.
ОтветитьУдалитьВидите ли, в чём дело... Интервью, которое организовал с Александром и Максимом Всеволод всё же недостаточно конкретно. В нём в крайне общем виде содержится сообщение, которое лично я воспринял как сведения о том, что существует компания, которая использует UML для программирования. Что в рамках технологии, разработанной и используемой в этой компании существует увязка графической нотации UML с программным кодом.
Вместе с тем, в интервью нет ответа на главный вопрос: *зачем* это делается?
В этом блоге я принимал участие в ряде обсуждений темы "зачем", в результате которых у меня сложились, возможно ошибочные, представления на этот счёт.
Представление проекта в виде диаграммы (или совокупности вложенных диаграмм) позволяет:
* Быстро представить себе его архитектуру
* Является всегда актуальной документацией к проекту
* Позволяет в полной мере воспользоваться типовыми решениями, описанными посредством шаблонов
Кодогенерация упрощает труд программиста, поскольку:
* Порождение понятий из существующих автоматически приводит к генерации соответствующих классов с выбранными способами порождения (наследование, агрегация, композиция, ассоциация и др.)
* Уже установление связи между понятиями в соответствии с выбранным стереотипом этой связи позволяет сформировать код, отражающий эту связь.
Почему-то у меня складывается ощущение, что я очень многое упустил.
Вместе с тем, эта информация крайне важна, поскольку любой разработчик (и я тоже) рассматривая какие-либо нововведения, ищет, как может их применит в своей работе. А руководитель (включая меня) оценивает соотношение затрат (вложения в инфраструктуру - переход на UML и кодогенерацию) и качеств достигаемого результата (большая стабильность, более низкий порог входа для новых членов команды, большая производительность труда).
Наверное, было бы очень интересно узнать историю появления такого взгляда (UML + кодогенерация) на решение проблем. При решении каких задач возникла идея воспользоваться кодогенерацией? Какие проблемы стремились решить? В какой момент возникло понимание того, что UML, предназначенный для проектирования уместно применить и для кода?
Относительно приведённого в этой замечательной статье примера я хочу выразить надежду, что во второй (или третьей) части будет описано более-менее практическое применение.
Я имею ввиду, что при разработке приложений мы часто сталкиваемся с типовыми формами, фреймами и подключением функциональности к ним, которые должны хорошо ложиться в концепцию "UML + codegen".
Это важно для того, чтобы оценить: какая именно работа ложится на разработчика, которого Александр называет "конечным" программистом. Этот программист, в сущности, и создаёт приложение. Поскольку целью любых инфраструктурных изменений, в итоге является повышение производительности труда, необходимо понять, что в итоге повышает производительность этого "конечного" программиста.
По боьшому счету все те цели которые вы перечислили выше, как раз и являются основными, дальше образуя так сказать суперпозицию этих бенефитов, мы получаете и низкий порог входа, и ускорение разработки, и повышение качества кода и тд. и тп.
УдалитьКак это пришло в голову я как раз рассказывал в интервью. Посмотрите еще раз. В двух словах, нужно было связать в одну цепочку сервер на с++, транспорт в виде CORBA, и клиентсую облочку на Дельфях (при этом без использования ВизиБрокера). Сложность начиналась там где в оболочку экспорртировались c++ интерфейсы из dll. Точнее это была не сложность, это был АД, пройти через который не преставлялось возможном, собтсвенно тогда и появилась идея, написать шаблон который бы обеспечивал прозрачную интероперабильность с++ <--> дельфи для объектных интерфейсов ПРОИЗВОЛЬНОЙ сложности описанных ОДИН РАЗ на UML модели. И нам это удалось сделать на удивление быстро, результатом пользуемся до сих пор (а это было больше 10 лет наверное уже назад).
Рассказал сегодня "на пальцах" коллеге, про "бинарную совместимость" интерфейсов Delphi и C++. Со всякими virtual public. Он - "впечатлился". Думаю так.
Удалить"упустил главную фишку - из-за обязательной кодогенерации модель не протухает вообще"
Удалить!!!! Коллега тут подсказал
Это к вопросу о:
Удалить"Вместе с тем, в интервью нет ответа на главный вопрос: *зачем* это делается?"
«Рассказал сегодня "на пальцах" коллеге, про "бинарную совместимость" интерфейсов Delphi и C++. Со всякими virtual public. Он - "впечатлился". Думаю так.»
Удалить-- Может и нам расскажете? А то я пока не понимаю, о чём речь.
«упустил главную фишку - из-за обязательной кодогенерации модель не протухает вообще"
!!!! Коллега тут подсказал»
-- Тут тоже непонятно.
«Это к вопросу о:
"Вместе с тем, в интервью нет ответа на главный вопрос: *зачем* это делается?"»
-- Да, вопрос актуален.
Хотя я пока ещё не всё изучил достаточно подробно...
"Может и нам расскажете? А то я пока не понимаю, о чём речь."
Удалить-- это надо ДЕТАЛЬНО рассказывать. Про "байты". Попробую как-нибудь. НО это - долгая история.
"Да, вопрос актуален."
Удалить-- про "мотивацию" - я думаю Макс лучше меня расскажет. Если захочет. У меня (как у "хардкорного" практика) мотивация одна - это СИЛЬНО помогает мне в моей работе. Ну я же в "байтах" варюсь. Мне - реально помогает. Даже "хичкоковский триллер" :-) Мне кстати - название понравилось..
"Тут тоже непонятно."
Удалить-- ну код - НЕЛЬЗЯ менять без изменения модели.. А если поменяешь МОДЕЛЬ - меняется код.. Они - ВЗАИМОСВЯЗАНЫ,, и они - АКТУАЛЬНЫ всё время.. МОДЕЛЬ - "не картинка".. МОДЕЛЬ - "чертёж" работающего кода... А код - отображение МОДЕЛИ, как "чертежа".. Это ПРАКТИКА такая.. Реально "практикуемая".. Простите за тавтологию..
"Может и нам расскажете? А то я пока не понимаю, о чём речь."
Удалить-- тут скажем так.. "Маршалинг, без маршалинга"... Tie-implementation...
скажем даже дальше - код на C++ поднимает исключение, код на Delphi его ПРОЗРАЧНО обрабатывает..
сложная тема...
но даже и этот "хоккей" при наличии "нашего UML" и кодогенерации - уже "незачем обсуждать".. Из нашей модели можно построить plain-DLL без интерфейсов.. и "объектные заглушки" на стороне Delphi.. без "вмешательства человека"..
Самый "близкий" пример - safecall..
«"Может и нам расскажете? А то я пока не понимаю, о чём речь."
ОтветитьУдалить-- это надо ДЕТАЛЬНО рассказывать. Про "байты". Попробую как-нибудь. НО это - долгая история.
... ... ...
тут скажем так.. "Маршалинг, без маршалинга"... Tie-implementation...»
-- Очень неплохо бы... Хотя бы, какая задача была. Как я понимаю, у вас работали две команды: одна разрабатывала серверную часть, другая - клиентскую. DLL - вероятно, клиентский DLL, посредством которого производилось взаимодействие клиентской части с серверной. На это наводит упоминание VisiBrocker и темы маршаллинга, которую Вы затронули.
Не знаю впрочем, чем не устроил VisiBrocker, тем более, если использовалась CORBA, почему не подошёл h2p, но вероятно, если отбросить эти варианты, проще было бе перейти к централизованному (не в C++ и не в Pascal) ведению интерфейса DLL, выбрав в качестве DSL XML или что-то вроде того.
В таком случае кодогенерация сведётся к синхронному формированию идентичных друг другу интерфейсов к DLL на C++ и на Pascal. Только вот зачем тут диаграммы - не пойму. Ну, наверное, если они были бы уже, но Максим сказал, что "это удалось сделать на удивление быстро" - не оговоров впрочем, что понимается под "это". То, что я описал в предыдущем абзаце - можно создать за 24 +/- 8 часа работы одного программиста. На поддержку кодогенерации и увязки её с UML, вероятно, было потрачено больше времени.
Приходится домысливать... Я ведь не знаю задачи, которая решалась...
«скажем даже дальше - код на C++ поднимает исключение, код на Delphi его ПРОЗРАЧНО обрабатывает..»
-- Ну опять же, что значит "прозрачно"? И зачем исключения в DLL, почему не код возврата, который можно стандартным образом обрабатывать, "заворачивая" его, при необходимости, в исключение в клиентском коде...
«сложная тема...
но даже и этот "хоккей" при наличии "нашего UML" и кодогенерации - уже "незачем обсуждать".. Из нашей модели можно построить plain-DLL без интерфейсов.. и "объектные заглушки" на стороне Delphi.. без "вмешательства человека"..
Самый "близкий" пример - safecall..»
-- Ну, в описанном мной варианте "кодогенерации" результат был бы тем же... Возможно, я чего-то не учитываю (Вы же не говорите), но пока для меня выглядит именно так.
Ну и не на пустом месте я это пишу :-) У нас ведь серверное приложение - на Java по Tomcat, исполняются на виртуальной машине на мэйнфрейме, клиентская часть - на Delphi. Взаимодействуем, понятно, через DLL Java, тоже без VisiBrocker, правда, и без CORBA тоже. "Сложности" маршаллинга локализованы в одном месте. О диаграммах и кодогенерации в контексте этой задачи даже не задумывались. Накладные расходы на синхронизацию интерфеса взаимодействия теряются на фоне других решаемых задач. Даже не возьмусь их оценивать. Не занимались этим специально. Ну, может, на начальном этапе - помню, там были какие-то вопросы...
Я это просто к тому, что проблемы можно решать разными средствами. Выбор средств зависит от преследуемой цели. Но свои цели я знаю, а ваши - нет.
тут - сложно ответить.. пусть - Макс - попробует..
Удалить""заворачивая" его, при необходимости, в исключение в клиентском коде..."
Удалить-- и "заворачивает".. Путём кодогенерации...
"выбрав в качестве DSL XML или что-то вроде того."
Удалить-- XML Тогда был в "зачаточном" состоянии, да и скорость - немаловажна была.
«""заворачивая" его, при необходимости, в исключение в клиентском коде..."
Удалить-- и "заворачивает".. Путём кодогенерации...»
-- Ну, я обозначил выше, что при определённом подходе (просто он - не единственный) кодогенерация может быть уместна. Но при чём здесь диаграммы? - Не пойму.
Вот чего вы от меня хотите? :-)
УдалитьМы - ТАК ДЕЛАЕМ... ПРИТОМ и "диаграммы"...
Что ЛЮБАЯ задача в НАШЕЙ области имеет МИЛЛИОН решений - я и так знаю..
И этот факт меня кстати раздражает...
И я пишу лишь о том, что МЫ ДЕЛАЕМ и КАК мы это делаем.. Я НИГДЕ не сказал, что это - ЕДИНСТВЕННО ВЕРНЫЙ путь... Я лишь пытаюсь "поделиться".. Не нравится - НЕ СЛУШАЙТЕ.. Только и всего... НРАВИТСЯ... Сказали "спасибо".. и то - хлеб.. ТОЛЬКО И ВСЕГО... Я НЕ "евангелирую" ничего...
Удалить>> чем не устроил VisiBrocker
Удалитьруководство покупать отказалось + у разработчиков на Дельфях тогда почему-то была устойчивая идиосинкразия к КОРБЕ (без причн, имхо)
>>можно создать за 24 +/- 8
ну я тоже не знаю что было у вас, но могу догатываться что вы описывали интерфейс на XMLа потом преобразовывали его в компланарные интерфейсы для с++ и дельфей. Наверное для простых или не очень сложных ситуация это работает. Но нам такой варимант не подходил. нуменклатура интерфейсов было ОЧЕНЬ большой, вариантов использования ОЧЕНЬ много. Простого компланарного описания не хватало, точнее ее просто не получалось сделать.. Нужны были классы обвязки и с той и с тдругой стороны. Грубо говоря, на с+= интерфейс мог выглядить так:
class ISomeFacet {
std::vector transform (ICallbackInterface call_back, SomeStruct&* some_struct, std::string& some_string) throw SomeException;
}
и вот он должен был читаться на со стороны дельфей, а ICallbackInterface (примерно такой же) еще и реализовываться. Разумеется один в один он не мог оказаться на Дельфях, поэтому на дельфях должен был быть ЭКВИВАЛЕНТНЫЙ интерфейс.
А вот на модели было то что я написал, и из него генерировались TIE-обвязки, которые уже МОГЛИ быть компланарными, и которые внутри себя проводили все преобразования с учетом разных типов, управления памятью и тд;
А про ХМЛ, был у нас один коллега, я в то время, как раз еще в самом начале, ему задачу поставил этот шаблон попробывать написать. У него мои %SN вызвали жуткое отторжение, и он долго меня убеждал что все это можно КАК РАЗ на XML сделать "за пару дней", ну "ок" говорю сделай, через пару дней он виновато продолжил с шаблонами ковыряться, через неделю или две я правд задачу у него забрал и сам все сделал, чего ж человека мучать, ну не нравились ему %SN, и так ни когда не понравились уже.
> Но свои цели я знаю, а ваши - нет.
Удалитьв тот момент цель была сделать работу с++ и дельфи программистов МАКСИМАЛЬНО не зависимой с тз разницы языков. Ну те что бы все было так будто они на ОДНОМ языке в ОДНОЙ среде работают. И это в общем-то получилось.
Это тоже про порг входа, приходил новый человек на с+=, и ему вобще не надо было задумываться что есть какой-то дельыи, и что поэжтому у него есть определенные ограничения на интерфейсы, и что есть длл, и что есть ХМЛ, и что грубо говоря std::string нельзя возвращать или ждать что он тебе приедит в ответ. А все что нужно было дык это научится рисовать квадратики СИЛЬНО похожие на UML, который к тому моменту потехоньку-полегоньку но все уже становился известным и изучаемым.
> Но при чём здесь диаграммы?
Удалитьну генерировать код нужно же было из чего-то? да можно было придумать что-то рядом в качестве метаописания, хоть XML хоть свой DSL, и натравливать на него ТОТ ЖЕ САМЫЙ наш шаблон с ТЕМ ЖЕ САМЫМ генератором и получать на выходе тоже самое. На зачем? Если уже есть инструмент который позволяет это же НАРИСОВАТЬ а не писать кучей каракулей. Два в одном. И быстрее и нагляднее. Хотя спустя годы мы уже и сами пришли к выводу что иногда хочется модель не рисовать а записать, но это уже другая тема )) и причины другие (типа мерджа моделей при асинхрнной работе)
«чем не устроил VisiBrocker
Удалитьруководство покупать отказалось + у разработчиков на Дельфях тогда почему-то была устойчивая идиосинкразия к КОРБЕ (без причн, имхо)»
-- Как-то странно выходит... Хотя понятно, что от руководства можно ждать чего угодно, но отказаться от приобретения VisiBrocker и, вместо этого, реализовать часть его функциональности самостоятельно...
Ведь в сущности, для того, что нашло отражение у Вас с коллегами в диаграммах, в VisiBrocker предназначен IDL. Если я правильно понял Вас, конечно...
«можно создать за 24 +/- 8
ну я тоже не знаю что было у вас, но могу догатываться что вы описывали интерфейс на XMLа потом преобразовывали его в компланарные интерфейсы для с++ и дельфей.»
-- Нет, у нас совершенно не так, мы взаимодействуем с Java, и совсем иначе. Но как у нас - несущественно.
Меня немного смутило обилие и разнообразие интерфейсов, используемых в DLL. В принципе, в CORBA их не так уж и много, в части маршаллинга, по крайней мере. То, что можно (допустимо) использовать, определяет IDL, которым и можно было бы и вдохновляться. В частности, маршаллинг std::string там не предусмотрен, т.е. сам DSL такое запрещает. Ну и на XML IDL ложится достаточно хорошо. Так что, думаю, подход через XML масштабируется в плане сложности в той же мере, что и IDL.
«Но при чём здесь диаграммы?
ну генерировать код нужно же было из чего-то? да можно было придумать что-то рядом в качестве метаописания, хоть XML хоть свой DSL, и натравливать на него ТОТ ЖЕ САМЫЙ наш шаблон с ТЕМ ЖЕ САМЫМ генератором и получать на выходе тоже самое. На зачем? Если уже есть инструмент который позволяет это же НАРИСОВАТЬ а не писать кучей каракулей.»
-- Т.е. инструмент для создания диаграмм и кодогенерация по шаблонам уже были :-)
Чтож, в таком случае, грех было не воспользоваться этими инструментами.
«Два в одном. И быстрее и нагляднее. Хотя спустя годы мы уже и сами пришли к выводу что иногда хочется модель не рисовать а записать, но это уже другая тема )) и причины другие (типа мерджа моделей при асинхрнной работе)»
-- Да-да... IMHO подобные вопросы возникают в отношении любой графической нотации...
БОЛЬШОЕ СПАСИБО за Ваши разъяснения. Многое становится понятным.
> Ведь в сущности, для того, что нашло отражение у Вас с коллегами в диаграммах, в VisiBrocker предназначен IDL. Если я правильно понял Вас, конечно...
Удалитьда ОЧЕНЬ похоже, IDL мы правда тоже с диаграмм генерировали )) это и было САМЫМ началом, но тогда именно хотелось всю наменкулаитуоур увидеть, те на самсом первом шаге, это да, именно способ проектирования + докуменация были. Именно на первом. Дык вот когда мы захотели "рисовать" IDL, выяснилось что РешаналРозе рисует еще ни чего но вот генерирует выходной IDL - пряимо скажем кое-как. Тогда собственно и возник свой генертор, который уже использовал шаблон (совсем-совсем примитивный, по сути это было аналогично XSLT преобразованию). А вот на втором шаге, когда возникла необходимости связать все это с Дельфи, мы это хозяйство и использовали, уже немного расширив возможности и метаописания и генератора.
> Меня немного смутило обилие и разнообразие интерфейсов,
Удалитьшутка в том что в дельфи мы отдавали уже не CORBA а свои интерфейсы, те клиентская длл была не просто переходником-трансформатором, а скорее контроллером (с тз MVC), на ней куча бизнесс-логики жило. Поэтому обилее и разнообразие было довольно большим. Очень большим.
Этот комментарий был удален автором.
Удалить«Меня немного смутило обилие и разнообразие интерфейсов,
Удалитьшутка в том что в дельфи мы отдавали уже не CORBA а свои интерфейсы, те клиентская длл была не просто переходником-трансформатором, а скорее контроллером (с тз MVC), на ней куча бизнесс-логики жило. Поэтому обилее и разнообразие было довольно большим. Очень большим.»
-- Тут, признаться, заинтриговали... :-)
А какой вообще смысл был в этой DLL? Почему всю бизнес-логику не реализовать на Delphi, если клиет всё равно становится толстым: Delphi-клиент + DLL с которым он работает, и в котором содержится бизнес-логика, пусть и не вся, есть ведь ещё сервер приложений, для взаимодействия с которым, как я понимаю, использовалась CORBA. Ну и пусть бы Delphi-клиент взаимодействовал с сервером приложений напрямую, через ту же CORBA.
Какой был конструктивный смысл в лице этого DLL на C++, если управляться с ним из Delphi чуть ли не сложнее, чем с сервером приложений через CORBA?
конструктивных смыслов было несколько но все они, увы, были в не технологичнской плоскости.
Удалить1) программистов на с++ было больше чем на Дельфи
2) покупать визиБрокер руквовдство не хотело
3) программисты на дельфи не хотели возиться с корбой
А в идеале, да конечно. Сервер на с++ <--- КОРБА ---> Клиент на Дельфи.
>чуть ли не сложнее, чем с сервером приложений через
ну вот в итоге, благодаря шаблонам, стало не то что "не сложнее", а ЗНАЧИТЕЛЬНО легче.
Спасибо за ответы по-существу.
Удалить«Вот чего вы от меня хотите? :-)
ОтветитьУдалитьМы - ТАК ДЕЛАЕМ... ПРИТОМ и "диаграммы"...
Что ЛЮБАЯ задача в НАШЕЙ области имеет МИЛЛИОН решений - я и так знаю..
И этот факт меня кстати раздражает...»
-- В этом месте - совершенно конкретного.
Вероятно - одного из двух:
1. Мотивации, почему применение диаграмм для получения решения конкретной задачи с DLL (с учётом затрат на создание инфраструктуры построения этих диаграмм) Вами воспринимается как самый оптимальный поход.
2. Указания на то, что методология диаграмм уже разрабатывалась для решения более широкого класса задач, а тема с DLL оказалось, возможно первым, её удачным применением.
Ну, возможно, есть третье и четвёртое, пятое и десятое, которых я сейчас просто не вижу.
Я ведь не из тех философов, которые по капле воды могут восстановить картину океанов и всех их обитателей. К сожалению.
«И я пишу лишь о том, что МЫ ДЕЛАЕМ и КАК мы это делаем.. Я НИГДЕ не сказал, что это - ЕДИНСТВЕННО ВЕРНЫЙ путь... Я лишь пытаюсь "поделиться".. Не нравится - НЕ СЛУШАЙТЕ.. Только и всего... НРАВИТСЯ... Сказали "спасибо".. и то - хлеб.. ТОЛЬКО И ВСЕГО... Я НЕ "евангелирую" ничего...»
-- Я же и говорю: СПАСИБО :-)
Не нравилось бы - писал бы сюда.
«Вот чего вы от меня хотите? :-)
ОтветитьУдалитьМы - ТАК ДЕЛАЕМ... ПРИТОМ и "диаграммы"...
Что ЛЮБАЯ задача в НАШЕЙ области имеет МИЛЛИОН решений - я и так знаю..
И этот факт меня кстати раздражает...»
-- Дополнительно.
В Вашем блоге Александр, говоря формально, я решаю две задачи:
1. Понять, как устроена Ваша с коллегами технология работы.
*** Зачем? Для того, чтобы расширить кругозор, получить представление о других подходах к разработке, иметь их ввиду, возможно задачи, с которыми мне придётся столкнуться в будущем, найдут конструктивное решение в таком ключе.
Наконец, программа третьего курса подразумевает знакомство с UML и шаблонами проектирования. Теперь я смогу дополнить часть, касающуюся применений UML сообщением, что "есть в Москве такая фирма, в которой UML применяется для программирования. И вот как они это делают в общих чертах". Кто знает, может чьё-то внимание это привлечёт. А мне, вероятно, придётся сделать больший упор на стереотипах и прецедентах, нежели до этого.
2. Понять, зачем Вам с коллегами потребовались диаграммы. В контексте решения каких задач Вы ощутили в них необходимость? И как их повсеместное внедрение сказалось на технологии разработки, которую Вы создали на их основе.
*** Зачем? - Понять: как я сам решал бы эти задачи и, если приемлемого (на мой вкус ;-)) решения я не найду, вероятно, мне следует заняться проработкой Вашего направления с тем, чтобы через некоторое время можно было бы у нас внедрить ключевые элементы технологии, подобной используемой Вами коллегами.
С другой стороны, наряду с достоинствами, которые я указал выше, мне хотелось бы понять, чего это будет стоить для всей технологии разработки. Ведь не секрет, что здесь Вы в большей степени концентрируетесь на достоинствах Вашего подхода, а мне важно увидеть и недостатки тоже.
Например, Максим утверждает, что порог входа понижается ввиду суперпозиции достоинств о которых Вы говорите. Честно говоря — не убеждён. И вот почему. Методология разработки, ориентированная на код, развивается очень давно, там уже достигнуты впечатляющие результаты, это преподаётся в ВУЗах (и мною в т.ч.). Известны типовые проблемы и способы их решения.
В отношении методологии, основанной на жёсткой связи диаграммы с кодом, отражении модели предметной области на языке диаграмм, а объектов и связей между ними — посредством шаблонов — неизвестно ничего, кроме того, что Вы с коллегами это придумали, используете и довольны.
Понижает ли это порог входа? - Сомневаюсь. Человеку нужно, в сущности, изучать совсем другой подход к программированию и, очень сомневаюсь, что через три месяца можно говорить о его (нового сотрудника) рентабельности.
Есть вообще экзистенциальные вопросы. Ну очень трудно сопоставить два настолько различных подхода. Приходится сосредотачиваться на частностях и путём их анализа надеяться на какие-то конструктивные обобщения. В этом ключе для меня очень большой интерес представлял цикл Ваших статей о Вашем с коллегами фреймворке. У меня нет сомнений в том, что он ориентирован на применение в контексте диаграмм. Вот и хотелось бы понять: в чём именно заключается эта ориентация.
УдалитьНу и типовые решения, конечно же. Не зря я Вас «пытал» относительно того, из чего следует, что связь между объектами приведёт при кодогенерации к порождению обработчиков событий (или как Вы там это называете в отношении publisher/subscriber). Теперь, с учётом материала от Максима понятно, что так транслируется стереотип связи между объектами. Ну, или может транслироваться — я же не знаю, как там у Вас с коллегами на самом деле.
Возвращаюсь к экзистенциальности. Смотрите: кодогенерация, шаблоны вообще — это, в сущности, автоматизация некоторого дублирования. Это ставит в тупик, поскольку парадигмой современного программирования является всяческий уход от дублирования.
За кодогенерацию у Вас с коллегами отвечают шаблоны. Но зачем эти шаблоны вообще нужны, если стереотипы должны быть максимально, в идеале — абсолютно простыми и дублирования кода быть не должно вообще, а должна быть только параметризацию существующего в его повторном использовании?
Рассмотрим здесь простой отвлечённый пример. Вынужден фантазировать, поскольку о том что у Вас с коллегами знаю только то, что Вы сообщили. Есть два объекта, для определённости publisher и subscriber. Мы устанавливаем между ними связь со стереотипом handle_publisher_event. При кодогенерации нам автоматически сформируют заготовку для обработчика событий и настоят этот обработчик. Вероятно, всё это будет выполнено в контексте того прецедента, где мы устанавливаем такую связь. Прецедент — вероятно, это класс (или даже объект). Для этого класса или объекта нужно где-то предусмотреть создание publisher и subscriber с установлением связи между ними.
Вот тут пошли варианты... А если publisher и subscriber расположены в *разных* прецедентах? А где будет прозведено создание экземпляра subscriber? В конструкторе прецедента или мне самому нужно писать код по созданию экземпляра subscriber?
Как решаются такие вопросы? Как в коде - я знаю. Очень просто. И человек сам разберётся, достаточно ему пример показать, навыков из ВУЗа с лихвой хватит, чтобы понять.
И таких вопросов очень много Александр. Некоторые другие аспекты, вызывающие у меня сомнения я изложил ранее, когда говорил о рефакторинге и работе «конечного» программиста. Это не «наезд», а стремление разобраться. Было бы очень интересно. Если бы Вы остановились на том, как этот «конечный» программист работает, с чем имеет дело, какие изменения в модели он может делать самостоятельно, что должен согласовывать, какие есть стандарты и типовые решения.
"Возвращаюсь к экзистенциальности. Смотрите: кодогенерация, шаблоны вообще — это, в сущности, автоматизация некоторого дублирования. Это ставит в тупик, поскольку парадигмой современного программирования является всяческий уход от дублирования."
Удалить-- понимаете какая штука. Дублирование может быть физическим, а может быть логическим. Конечно идеально, чтобы ни того, ни другого не было бы. Но! Вот скажем std::vector(int) и std::vector(std::string) это один код?
Ведь нет конечно. Ведь шаблоны C++ это же тоже ПО СУТИ - "макроподстановки". Соответственно - физическое дублирование - присутствует, а вот логическое - нет. Так и с нашими шаблонами кодогенерации.
Логика сосредоточена в ОДНОМ МЕСТЕ, а вариантов инстанцирования - да - несколько.
Но от этого - никуда не деться. Нет ну можно конечно делать "полиморфные" контейнеры. Но в ущерб производительности. Ну или посредством "хоккея" на уровне компилятора. Типа boxing в C#.
Это не следует читать, как то, что я ПРИЗЫВАЮ к дублированию. Нет! Не призываю. И всеми силами стараюсь от него избавляться. Но! Просто надо ЧЁТКО осознавать, что в выборе эффективность/простота использования/дублирование - дублированию - ТОЖЕ место находится. И хорошо, если это дублирование лишь физическое, а не логическое.
"А если publisher и subscriber расположены в *разных* прецедентах? "
Удалить-- насколько я "трактую" - РАЗНЫЕ прецеденты - не могут "взаимодействовать", они лишь могут СМЕНЯТЬ друг друга. Ну это Я ТАК понял RUP. И так применяю на практике.
Речь КОНЕЧНО о КОНКРЕТНЫХ прецедентах, а не об абстрактных.
УдалитьИ Generic'и Delphi - это ТОЖЕ - ДУБЛИРОВАНИЕ кода. Физическое. О чём ЯВНО в документации и написано. Есть "предкомпиляция" Generic'ов в код Unit'а в котором он содержится и есть "инстанцирующая компиляция" по месту использования.
УдалитьВот пока нашёл цитату про FreePascal:
Удалить"Technical details
1. The compiler parses a generic, but instead of generating code it stores all tokens in a token buffer inside the PPU file.
2. The compiler parses a specialization; for this it loads the token buffer from the PPU file and parses that again, but replaces the generic parameters (in most examples "T") by the particular given type (e.g. LongInt, TObject). The code basically appears as if the same class had been written as the generic but with T replaced by the given type.
Therefore in theory there should be no speed differences between a "normal" class and a generic one."
http://wiki.freepascal.org/Generics
"При кодогенерации нам автоматически сформируют заготовку для обработчика событий и настоят этот обработчик."
Удалить-- не совсем так. При кодогенерации нам добавят НАСЛЕДОВАНИЕ от класса описанного на той де модели. Возможно - примесное. Subscriber'у своё, Publisher'у - своё. А возможно нам сделают "утилитный" приватный класс. С правильным наследованием (читайте - аспект), который выполняет роль либо publisher'а, либо subscriber'а. Реализующий часть "шаблона проектирования" publisher/subscriber. И транслирующий "клиентские" вызовы в породивший его класс.
Вот и цитата про Generic'и в Delphi:
Удалить"Instantiation
The compiler generates real instruction code for methods defined in generics and real virtual method table for a closed constructed type. This process is required before emitting a Delphi compiled unit file (.dcu) or object file (.obj) for Win32.
"
ms-help://embarcadero.rs_xe3/rad/Terminology_for_Generics.html
Собственно это же подтверждают и исследования ассемблерного кода сгенерированного из Generic'ов. Адреса методов - для РАЗНЫХ "экземпляров инстанцирования Generic'ов" - РАЗНЫЕ.
УдалитьЧто собственно - понятно. По-другому - не сделаешь. Разные типы имеют РАЗНЫЙ набор базисных операций. По-разному на УРОВНЕ физического кода реализованные.
Никакой магии.
Считайте - всё та же "перегрузка операторов". На уровне компилятора.
Про C++
Удалитьhttp://ru.wikipedia.org/wiki/%D8%E0%E1%EB%EE%ED%FB_C++
"Хотя шаблоны предоставляют краткую форму записи участка кода, на самом деле их использование не сокращает исполнимый код, так как для каждого набора параметров компилятор создаёт отдельный экземпляр функции или класса."
-- ОПЯТЬ ЖЕ - имеем ФИЗИЧЕСКОЕ дублирование КОДА, но ЛОГИЧЕСКИ - он один.
Собственно "наши" шаблоны - в ЭТОМ ПЛАНЕ - не лучше и не хуже.
"компилятор создаёт отдельный экземпляр функции или класса"
Удалить-- никакой МАГИИ...
"У меня нет сомнений в том, что он ориентирован на применение в контексте диаграмм."
Удалить-- это кстати - НЕ ТАК. Фреймворк для "конечного" программиста - прекрасно живёт и БЕЗ диаграмм. ВЕСЬ код, основанный на фреймворке - можно писать РУКАМИ. И есть проекты, в которых ТАК И СДЕЛАНО. Другое дело, что модель "помогает" писать этот код. С моделью - проще. Но можно и РУКАМИ.
Например - можно просто ДЕКЛАРИРОВАТЬ на модели, что "контрол данного класса реализует вот такие пользовательские операции GUI (Cut, Copy, Paste к примеру)" и посредством кодогенератора будет создана соответствующая "обвязка" для РЕАЛИЗАЦИИ контролом нужных интерфейсов и регистрации их в нужных "точках подключения". А можно ВСЁ то же самое - написать РУКАМИ. Фреймфорк -в этом плане - ни к чему НЕ ПРИНУЖДАЕТ. И на самом деле НА ПРАКТИКЕ - нами используются ОБА подхода. Исходя из соображений эффективности. Жаль вот Виктор Морозов не комментирует всё это. Он бы мог рассказать - как он РУКАМИ писал многие вещи, которые можно было бы сделать при помощи модели, просто потому, что он чего-то "не знал" или "так было быстрее".
Удалить"И на самом деле НА ПРАКТИКЕ - нами используются ОБА подхода."
Удалить-- "магии" нет никакой. Всё в конечном итоге приходит к КОДУ. Пусть и не написанному руками. Но "как-будто" руками написанному. Он точно так же компилируется и точно так же выполняется. И вполне может быть написан РУКАМИ.
О!
УдалитьВот пример:
MyClass = class
MyInterface = interface
ведь эквивалентно:
MyClass = class(TObject)
MyInterface = interface(IUnknown)
а почему? потому, что компилятор "подставляет" нужные значения.
Так и у нас. Модель и кодогенератор - многое "подставляют".
Написали:
MyDocumentForm = VCMFrom
получили:
MyDocumentForm = clas(TvcmForm)
Написали:
MySpecialDocumentForm = VCMForm(MyDocumentForm)
получили:
MySpecialDocumentForm = class(MyDocumentForm)
-- ну и далее - по аналогии.
Ну и ещё - модель многое ВАЛИДИРУЕТ. Она не даёт нарисовать такую модель, которая не укладывается в рамки "мета-модели". А руками это написать - ЗАПРОСТО. Но тут вот как раз модель выступает в роли "тьютора", она - "советует" ПРИНЯТЫЕ на данный момент проектные решения и ОТВЕРГАЕТ - "заведомо неправильные". На ДАННЫЙ МОМЕНТ. Если такое решение ВСЁ-ТАКИ нужно - варианта - ДВА, либо писать РУКАМИ, либо ПЕРЕСМАТРИВАТЬ мета-модель.
УдалитьАнглийская версия wiki говорить опять же о ФИЗИЧЕСКОМ дублировании кода:
Удалить"Both macros and templates are expanded at compile time. Macros are always expanded inline, while templates are only expanded inline when the compiler deems it appropriate. When expanded inline, macro functions and function templates have no extraneous runtime overhead. Template functions with many lines of code will incur runtime overhead when they are not expanded inline, but the reduction in code size may help the code to load from disk more quickly or fit within RAM caches."
http://en.wikipedia.org/wiki/Template_(C%2B%2B)
Т.е. - опять же получаем, что "наши" шаблоны для "UML" - в этом плане - ничуть не хуже и не лучше - СУЩЕСТВУЮЩИХ общепринятых практик.
И ещё оттуда же:
Удалить"Since the compiler generates additional code for each template type, indiscriminate use of templates can lead to code bloat, resulting in larger executables.
Because a template by its nature exposes its implementation, injudicious use in large systems can lead to longer build times."