Матрёшка: использование шаблона «визитёр» для обхода элементов UML моделиВ прошлой статье (Матрёшка: работа с UML моделями на примере генератора SQL DDL) рассматривался пример генератора кода создания схемы базы данных по UML модели. Для анализа элементов UML модели выполнялся простой обход всех загруженных элементов. Такой механизм обработки хорош для маленьких примеров, но в реальной жизни может потребовать написание большого количества кода для классификации элементов. Для исключения написания этого кода классически используется шаблон «визитёр», предоставляющий уже готовый механизм классификации и требующий от пользователя только необходимости определить выполняемые действия для каждого из классов элементов модели. ОбзорБазовая реализация шаблона «визитёр» находится в пакете AMF.Visitors.UML_Containment. Обход элементов UML модели при этом осуществляется в порядке вложенности элементов, от объемлющей модели вглубь. Для каждого неабстрактного класса метамодели UML базовая реализация располагает тремя подпрограммами, например для метакласса UML::Class: not overriding procedure Enter_Class (Self : in out UML_Containment_Visitor; Element : not null AMF.UML.Classes.UML_Class_Access) is null; not overriding procedure Visit_Class (Self : in out UML_Containment_Visitor; Element : not null AMF.UML.Classes.UML_Class_Access); not overriding procedure Leave_Class (Self : in out UML_Containment_Visitor; Element : not null AMF.UML.Classes.UML_Class_Access) is null; При обходе элементов UML модели для кажого элемента вызываются все три подпрограммы. Первой производится вызов подпрограммы Enter_<Class>, потом производится вызов подпрограммы Visit_<Class> и после этого — вызов Leave_<Class>. Как видно из кода, подпрограммы Enter_<Class> и Leave_<Class> имеют пустую реализацию по умолчанию. Их основное назначение — переопределение пользователем для реализации логики приложения. Подпрограмма Visit_<Class> по умолчанию выполняет последовательный обход каждого дочернего элемента текущего элемента UML модели. Её переопределение пользователем предполагается только на случай необходимости модификации последовательности обхода элементов UML модели. Переработка генератора кодаДля реализации генератора кода с использованием шаблона «визитёр» нам достаточно объявить новый тип, порождённый от типа AMF.Visitors.UML_Containment.UML_Containment_Visitor и переопределив подпрограммы Enter_Class: private with AMF.UML.Classes; with AMF.Visitors.UML_Containment; package Generators is type Generator is new AMF.Visitors.UML_Containment.UML_Containment_Visitor with private; private type Generator is new AMF.Visitors.UML_Containment.UML_Containment_Visitor with record In_Model : Boolean := False; end record; overriding procedure Enter_Class (Self : in out Generator; Element : not null AMF.UML.Classes.UML_Class_Access); end Generators; Реализация же будет выглядеть следующим образом: with Ada.Wide_Wide_Text_IO; with AMF.Elements; with AMF.UML.Elements; with AMF.UML.Models; with AMF.UML.Properties.Collections; with AMF.UML.Types; with League.Strings; package body Generators is use Ada.Wide_Wide_Text_IO; use type AMF.Optional_String; function "+" (Item : Wide_Wide_String) return League.Strings.Universal_String renames League.Strings.To_Universal_String; ----------------- -- Enter_Class -- ----------------- overriding procedure Enter_Class (Self : in out Generator; Element : not null AMF.UML.Classes.UML_Class_Access) is use type AMF.Optional_Integer; Owned_Attribute : constant AMF.UML.Properties.Collections.Ordered_Set_Of_UML_Property := Element.Get_Owned_Attribute; Attribute : AMF.UML.Properties.UML_Property_Access; First : Boolean := True; Attribute_Type : AMF.UML.Types.UML_Type_Access; Owner : AMF.UML.Elements.UML_Element_Access; begin Owner := AMF.UML.Elements.UML_Element_Access (AMF.Elements.Element_Access (Element).Container); if Owner.all not in AMF.UML.Models.UML_Model'Class or else AMF.UML.Models.UML_Model_Access (Owner).Get_Name /= +"Schema" then -- Return immediately when owner of current UML::Class is not -- UML::Model called "Schema". return; end if; Put ("CREATE TABLE " & Element.Get_Name.Value.To_Wide_Wide_String); for J in 1 .. Owned_Attribute.Length loop Attribute := Owned_Attribute.Element (J); Attribute_Type := Attribute.Get_Type; if not Attribute.Is_Multivalued then if First then New_Line; Put (" ("); First := False; else Put_Line (";"); Put (" "); end if; Put (Attribute.Get_Name.Value.To_Wide_Wide_String); Put (' '); Put (Attribute_Type.Get_Name.Value.To_Uppercase.To_Wide_Wide_String); if Attribute.Lower_Bound = 1 then Put (" NOT NULL"); end if; end if; end loop; Put_Line (");"); end Enter_Class; end Generators; Код генерации в точности такой же, как и в предыдущей статье, поэтому его обсуждение здесь опущено. Однако, в самом начале подпрограммы добавлена весьма важная проверка, выполняющая генерацию кода только если текущий класс находится в моделе с именем «Schema». С одной стороны это позволяет в одной моделе иметь описание не только схемы, но и других составляющих проекта; с другой стороны это позволяет обойти генерацию кода для огромного количества классов, импортируемых современными UML моделерами как бесплатное дополнение к модели пользователя. Из всего кода проверки хочется обратить внимание на способ получения «родительского» элемента. Структура AMF построена таким образом, что любой элемент любой модели всегда поддерживает интерфейс AMF.Elements.Abstract_Element; однако он не наследуется открыто для исключения прямой видимости его операций в коде приложения. Среди его операций имеется операция для получения «родительского» элемента, именуемая Container. Именно она и используется для получения «родительского» элемента, который с помощью преобразования типа приводится к базовому типу элементов UML модели. Осталось сделать последний шаг — написать основную подпрограмму: with AMF.Facility; with AMF.URI_Stores; with League.Application; with XMI.Reader; with AMF.Internals.Factories.UML_Factory; -- This package should be included into partition closure to be able to -- process UML models. with Generators; procedure Demo is Store : AMF.URI_Stores.URI_Store_Access; Generator : Generators.Generator; begin AMF.Facility.Initialize; -- Loading specified model Store := XMI.Reader.Read_File (League.Application.Arguments.Element (1)); -- Generate code Generator.Visit (Store); end Demo; Изменение порядка обхода элементовКак уже было сказано, обход дочерних элементов выполняют подпрограммы Visit<Class>. Пользователь вправе переопределить эти подпрограммы при необходимости изменения очередности обхода. При написании собственных подпрограмм обхода возникает необходимость классификации элемента и вызова его обработчика. Пользователь может использовать (и, более того, настоятельно рекомендуется её использовать) уже определённую подпрограмму: procedure Visit (Self : in out UML_Containment_Visitor'Class; Element : not null access AMF.UML.Elements.UML_Element'Class); Подпрограмма Visit выполняет классификацию указанного элемента и вызывает соответствующие метаклассу элемента подпрограммы Enter_<Class>, Visit_<Class> и Leave_<Class>; тем самым избавляя пользователя от необходимости самостоятельно выполнять эти действия для сотен метаклассов UML. Автор: Вадим Годунко |