Capítulo 4. Manipulación de Modelos UML

Tabla de contenidos

4.1. Introducción
4.1.1. Estándares de la OMG
4.1.2. Modelos vs. Diagramas
4.2. Creación programática de modelos
4.3. Serialización de modelos a XMI
4.4. Deserialización de modelos desde XMI
4.5. Propiedades con distinta granularidad

4.1. Introducción

4.1.1. Estándares de la OMG

El Lenguaje de Modelado Unificado establece un conjunto de convenciones ampliamente aceptadas para representar los conceptos relacionados al análisis y diseño orientado a objetos (OOAD). nUML provee la librería NUML.Uml2 para manipular modelos UML, tratando de cumplir con la versión 2.0 del estándar (UML 2.0 Superstructure FTF convenience document).

Otro estándar de la OMG (Object Management Group) soportado parcialmente por nUML es XMI: XML Metadata Interchange versión 2.0, en la librería NUML.Xmi2. Este estándar especifica como serializar modelos utilizando XML, y permite que distintas herramientas de modelado puedan intercambiar modelos entre sí.

El soporte de estos dos estándares es de vital importancia para el desarrollador de generadores de código, ya que le abren un abanico de posibilidades al permitirle utilizar cualquier herramienta de modelado UML que soporte XMI 2.1 para crear los modelos que serán el punto de entrada de su generador de código. También es posible usar herramientas para UML 1.4 y XMI 1.2 y otras variantes, gracias a la existencia de un conjunto de archivos XSLT que permiten su transformación a UML 2.0 sobre XMI 2.1.

Este capítulo supone que el lector tiene conocimientos mínimos de UML, al menos los conceptos relacionados a los diagramas de clases y paquetes.

4.1.2. Modelos vs. Diagramas

Cuando se habla de UML, por lo general vienen a la mente imágenes de cajas unidas por líneas. Estas cajas y líneas representan distintos conceptos del OOAD, tales como Clase y Asociación, y estas representaciones se encuentran expresadas en un diagrama.

Pero en un ambiente de generación de código, no nos interesan la posición ni el tamaño de las cajas, ni tampoco el grosor ni color de las líneas; nos interesan los conceptos representados por estas imágenes. El conjunto de estos conceptos representados es lo que llamamos Modelo, y es en definitiva el diseño que el autor del diagrama quiso representar gráficamente.

4.2. Creación programática de modelos

Utilizando las clases e interfaces de NUML.Uml2 podemos crear modelos mediante programación.

El espacio de nombres NUml.Uml2 provee una interfaz por cada metaclase[1] definida en el estándar, y en cada una de estas interfaces están definidos los métodos y propiedades especificados en el estándar UML 2.0; por lo tanto, una fuente excelente de documentación es el mismo estándar, donde están perfectamente explicados todos los elementos del UML así como sus relaciones.

Además de proveer estas interfaces, la librería expone una clase llamada Create, que permite crear objetos que implementan estas interfaces. Por cada metaclase concreta del UML hay un método correspondiente para crear instancias. Por ejemplo, para crear un paquete UML, basta con hacer lo siguiente:

	NUml.Uml2.Package mypkg = NUml.Uml2.Create.Package();

Una vez que obtuvimos una referencia a un objeto, es posible utilizar sus propiedades y métodos para modificar sus características; por ejemplo:

	mypkg.Name = "Mi_Paquete";

asigna el nombre Mi_Paquete al paquete que acabamos de crear.

Las relaciones entre objetos se manejan también mediante sus propiedades. Por ejemplo, para agregar una clase al conjunto de tipos propios del paquete, podemos hacer lo siguiente:

	NUml.Uml2.Class cls = NUml.Uml2.Create.Class();
	cls.Name = "Mi_Clase";
	// agrega la clase al conjunto de tipos propios del paquete
	mypkg.OwnedType.Add(cls);
	// le indica a la clase que su paquete es mypkg
	cls.Package = mypkg;

4.3. Serialización de modelos a XMI

Los modelos representados como objetos de la librería NUML.Uml2 pueden serializarse al formato XMI 2.1 mediante el uso del mecanismo de serialización provisto por NUml.Xmi2 y por NUml.Uml2.Serialization.

La librería de serialización XMI está diseñada para manejar objetos de varios ensamblados y en varios ficheros simultaneamente. Esta flexibilidad se paga en parte con una mayor complejidad de las interfaces que debe utilizar el programador usuario, pero si tenemos en mente estas características todo tiene sentido.

El proceso de serialización es conducido por una instancia de la clase SerializationDriver.

	NUml.Xmi2.SerializationDriver ser = new NUml.Xmi2.SerializationDriver();

Un conductor de serialización no puede hacer mucho por sí mismo; necesita de serializadores especializados, uno por cada librería a serializar. La librería de UML provee un serializador, cuyo nombre completo es NUml.Uml2.Serialization.Serializer. Para agregar seriazadores al conductor de serialización se utiliza el método AddSerializer:

	ser.AddSerializer( new NUml.Uml2.Serialization.Serializer() );

Una vez que tenemos configurado nuestro serializador, podemos utilizar cualquiera de las distintas sobrecargas del método Serialize para generar un fichero XMI en base a nuestro modelo:

	ser.Serialize( mypkg, System.Console.OpenStandardOutput(), "http://just_a_sample/" );

Esta última llamada merece una explicación. El método Serialize tiene varias sobrecargas, lo cual es cómodo porque se ajusta a los distintos usos que se puede llegar a darle. En este caso estamos pasando el modelo que queremos serializar (el objeto mypkg), el Stream correspondiente a la salida estándar, y un string. Este último parámetro sirve para identificar al dominio de modelo actual, que en este caso representa al stream que le estamos pasando. Como ya se mencionó, la librería permite manejar varios ficheros a la vez; cada fichero es un dominio de modelo, y necesita su propia URI. En el caso de los ficheros, la ubicación del mismo es su URI, pero como en este caso estamos pasando un Stream, la librería nos exige que proveamos un identificador para el elemento de modelo.

El programa completo se puede compilar con la siguiente línea de comandos (suponiendo que todas las líneas de código se han agregado al método Main):

	mcs -r:NUML.Xmi2.dll -r:NUML.Uml2.dll -out:test.exe Test.cs

La salida es la siguiente[2]:

<?xml version="1.0" encoding="utf-8"?>
<xmi:XMI
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmi:version="2.1"
  xmlns:uml="http://schema.omg.org/spec/uml/2.0/uml.xmi"
  xmlns:xmi="http://www.omg.org/XMI"
>
  <uml:Package xmi:id="993a3de9-4561-4031-b560-f7044042c2e3" name="Mi_Paquete">
    <ownedType xmi:id="64e6a198-50bb-4859-b72c-5c4b36dc1fd2" xsi:type="uml:Class" name="Mi_Clase" />
  </uml:Package>
</xmi:XMI>

Hay que tener en cuenta que los valores de los atributos xmi:id seguramente serán distintos en cada ejecución del programa.

4.4. Deserialización de modelos desde XMI

Así como es posible serializar un grafo de objetos en un fichero, también es posible realizar la operación inversa. Esto se logra utilizando alguna de las sobrecargas del método Deserialize. Por ejemplo:

public class TestDeserialization
{
	public static void Main(string[] args)
	{
		NUml.Xmi2.SerializationDriver ser = new NUml.Xmi2.SerializationDriver();
		ser.AddSerializer( new NUml.Uml2.Serialization.Serializer() );
		System.Collections.IList deserialized = ser.Deserialize ("test.xmi");
	}
}

En este caso también es necesario inicializar el conductor de serialización con los serializadores que hagan falta, según sea el contenido que querramos deserializar. Como resultado de la deserialización se obtiene un IList con la lista de elementos de nivel raíz. Por ejemplo, si deserializamos el siguiente fichero:

<?xml version="1.0" encoding="utf-8"?>
<!-- guardar como test.xmi -->
<xmi:XMI
  xmi:version="2.1"
  xmlns:xmi="http://www.omg.org/XMI"
  xmlns:uml="http://schema.omg.org/spec/uml/2.0/uml.xmi"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
  <uml:Package xmi:id="1" name="Mi_Paquete">
    <ownedType xmi:id="2" xsi:type="uml:Class" name="Mi_Clase" />
  </uml:Package>
</xmi:XMI>

obtendremos un IList con un solo elemento, en este caso un Package de UML.

Una vez deserializado el modelo, contamos con un grafo de objetos que puede ser navegado utilizando las mismas propiedades y métodos que vimos en Sección 4.2, “Creación programática de modelos”.

4.5. Propiedades con distinta granularidad

Una característica muy interesante del UML 2, que está disponible en la librería NUml.Uml2, es la composición de propiedades al estilo de conjuntos y subconjuntos.

Esta idea tal vez sea más fácil de transmitir explicando algún uso concreto, por ejemplo la misma especificación del UML 2. En UML, todas las metaclases derivan de Element. Element tiene un atributo[3] llamado ownedElement[4], que contiene a todos los elementos que son propiedad del elemento en cuestión.

Element también tiene un atributo ownedComment, que contiene a todos los comentarios que son propiedad del elemento. Lo interesante es que ownedComment es un subconjunto de ownedElement, y por lo tanto todos los comentarios que pertenecen a ownedComment figuran también en la colección ownedElement.

Esto se puede demostrar con este sencillo programa:

// Test.cs - para compilar:
// mcs -r:NUml.Uml2.dll -out:test.exe Test.cs
using System;
using UML = NUml.Uml2;
public class TestDeserialization
{
	public static void Main(string[] args)
	{
		UML.Class cls = UML.Create.Class();
		UML.Comment comment = UML.Create.Comment();
		comment.Body = "Este es un comentario.";
		cls.OwnedComment.Add(comment);
		Console.WriteLine("ownedComment contiene al comentario? " 
			+ cls.OwnedComment.Contains(comment));
		Console.WriteLine("ownedElement contiene al comentario? " 
			+ cls.OwnedElement.Contains(comment));
		foreach(UML.Element element in cls.OwnedElement)
		{
			UML.Comment c = element as UML.Comment;
			if(c != null)
			{
				Console.WriteLine("comentario encontrado: " + c.Body);
			}
		}
	}
}

Recordemos que los atributos ownedComment y ownedElement están definidos en Element, pero como todas las metaclases derivan de Element, Class también cuenta con ellos.

La salida es:

ownedComment contiene al comentario? True
ownedElement contiene al comentario? True
comentario encontrado: Este es un comentario.

UML define una gran cantidad de metaclases y atributos, la mayoría de los cuales forman relaciones de subconjuntos. Se recomienda consultar el estándar para conocer las posibilidades con las que contamos, ya que en muchas ocasiones son de utilidad a la hora de escribir generadores de código.



[1] Para distinguir las clases propias de un usuario (por ejemplo Persona, Cliente, Banco) de las clases que definen al UML, a estas últimas se las llama metaclases.

[2] Las declaraciones de espacios de nombres XML se distribuyeron en varias líneas para mejorar la legibilidad.

[3] En UML, un atributo vendría a ser lo que en C# llamamos un campo; no debe confundirse con los atributos de C#, que se utilizan para agregar metadatos a clases, métodos y demás.

[4] La librería utiliza la convención de nombres Pascal, razón por la cual la propiedad correspondiente se llama OwnedElement.