Está en la página 1de 12

Interpretación Ejecutable de Modelos Estructurales UML

Enriquecidos con Restricciones OCL


J. M. Cañete, F. J. Galán, M. Toro
Dep. Lenguajes y Sistemas Informáticos, Facultad de Informática
Avenida Reina Mercedes s/n c.p.: 41012-SEVILLA-ESPAÑA
Fax: 95 4557139 e-mail: {canete@arturo.lsi.us.es, galanm@arturo.lsi.us.es}

Resumen

Los formalismos de especificación gráfico-textuales para el análisis de sistemas orientados a


objetos presentan un importante equilibrio entre cantidad de información, precisión y legibilidad.
Nuestro interés se centra en traducir, automáticamente, especificaciones de sistemas expresados
gráfico-textualmente a programas ejecutables. La corrección y no interactividad en la traducción
serán las características más importantes de nuestro método.
Palabras claves: sistemas orientados a objetos, modelado,restricciones, unified modelling language,
object constraint language, especificación ejecutable, corrección, equivalencia semántica,
metamodelo.

1. Introducción

Una técnica ampliamente usada en ingeniería del software es modelar un sistema mediante combinación de vistas del
sistema que son semánticamente compatibles [CD94]. El primer beneficio de esta aproximación es un mejor manejo
de los sistemas complejos y detección de inconsistencias en etapas tempranas del análisis. Normalmente, el
modelado se compone de una vista estructural, otra dinámica y otra funcional.
El modelo estructural describe la relación entre las clases de componentes del sistema y la configuración de los
propios componentes del mismo. El modelo dinámico describe el ciclo de vida de los componentes del sistema y el
modelo funcional describe las transformaciones de datos, e invariantes del sistema.
Llamamos formalismos de especificación gráfico-textual a notaciones gráficas (ej. Object Modelling Technique
(OMT), notación de Booch, OOSE, Unified Modelling Language (UML), ...etc.) más una notación formal para
especificar restricciones que se integra como texto en la notación anterior (ej. subconjuntos de Z o VDM, OCL, etc).
Esto nos permite disponer de precisión a través del lenguaje de restricciones sin perder el impacto visual de la parte
gráfica. Por otra parte, las notaciones gráficas de análisis (y diseño) son más accesibles que las notaciones asentadas
en la teoría formal de conjuntos.
El formalismo gráfico-textual adoptado en este trabajo es la combinación de UML (Unified Modelling Language)
[UML971] para la parte gráfica y Object Constraint Language (OCL) [OCL97] como lenguaje formal para
especificar restricciones. No ha habido una razón especial para la elección de UML, si bien nos parece una notación
que fusiona las notaciones Booch, OMT y OOSE. Sin embargo, si lo ha habido para la elección de OCL ya que se
trata de un lenguaje de fácil lectura y escritura y puede ser ejecutable sin necesidad de una traducción compleja.

OCL se caracteriza por ser un lenguaje sin efectos laterales; por tanto, no altera el estado del sistema. Se trata de un
lenguaje tipado; esto significa que toda expresión OCL tiene un tipo. En una expresión OCL todos los tipos deben
ser conformes; por ejemplo no podemos comparar un entero con una cadena. Toda expresión OCL es
conceptualmente atómica. El estado de los objetos en el sistema no puede cambiar durante su evaluación. Usaremos
OCL para especificar invariantes sobre clases y tipos en el modelo de clases, describir pre y post condiciones de
operaciones y métodos, describir guardas y como lenguaje de navegación a través del modelo.

Los diagramas de UML son: diagramas de objetos que representan los objetos y sus relaciones, diagramas de
secuencia que representan las interacciones de los objetos en el tiempo, diagramas de estado-transición que
representan el comportamiento de una clase en términos de estado, diagramas de clases que representan la estructura
estática en términos de clase, diagramas de actividades que representan el comportamiento de una operación en
términos de acciones, los diagramas de casos de uso que representan las funciones del sistema desde el punto de
vista del usuario, diagramas de colaboración que son una representación espacial de objetos, enlaces e interacciones,
diagramas de componentes que representan los componentes físicos de una aplicación y diagramas de despliegue
que representan el despliegue de componentes sobre los dispositivos físicos. Un ejemplo de modelo de clases es el
que aparece en la figura 1. El significado de dicho modelo es el siguiente: Estamos en el contexto de la universidad
donde los profesores vienen representados por la clase Profesor, los alumnos por la clase Alumno, los departamentos
por la clase Departamento, las asignaturas por la clase Asignatura, las universidades por la clase Universidad y las
personas de nuestro contexto por la clase Persona. Consideremos la generalización Persona como disjunta (toda
persona o es profesor o es alumno de manera exclusiva) y es completa (toda persona de nuestro interés es profesor o
alumno de manera obligatoria). La asociación entre Profesor y Alumno significa que todo profesor es tutor de
proyectos para 0 o más alumnos (0..*) y que todo alumno puede tener o no a un profesor como tutor. El resto de las
asociaciones se interpreta de manera análoga: todo profesor está en un sólo departamento y el departamento está
asociado con al menos un profesor, la asociación reflexiva de Profesor a Profesor significa que un profesor es tutor
de 0 o más profesores a los que les dirige su doctorado, .... y así con el resto de las asociaciones. La asociación con
terminación en diamante consiste en una agregación; con ella expresamos que una universidad está formada por al
menos 1 departamento y que los departamentos están subordinados a las universidades.
Figura 1: Especificación UML / OCL
2. El lenguaje OCL

Cualquier expresión OCL está escrita en el contexto de una instancia de un tipo/clase definido en un modelo UML,
en el cual la expresión toma pleno significado. OCL define un conjunto de tipos con operaciones asociadas. Todas
las expresiones OCL ligadas a un modelo amplían ese conjunto inicial con todos los tipos/clases definidos en el
modelo. OCL permite además definir tipos enumerados en el ámbito de un modelo, que también pasarían a formar
parte del conjunto de tipos. Podemos clasificar los tipos predefinidos en básicos y de colección. Los primeros son
Integer (números enteros), Real (números reales), Boolean (valores lógicos), String (caracteres y cadenas, sin
distinción semántica), Enumeration (cuyas instancias son las enumeraciones definidas en un modelo), OclExpression
(cuyas instancias son todas las posibles expresiones que pueden escribirse en OCL), OclAny (supertipo para todos
los tipos del modelo) y OclType (cuyas instancias son los tipos del modelo y los predefinidos en OCL). OclType
permite, por tanto, acceder al meta-nivel del modelo. Los tipos de colección son Collection (supertipo abstracto para
los demás tipos de colección), Set (que representa el conjunto matemático), Bag (o multiconjunto, que es un conjunto
en el que se permite elementos duplicados) y Sequence (que puede verse como un multiconjunto en el que los
elementos están ordenados). Los tipos de colección son genéricos, y deben ser instanciados con el tipo de los
elementos.
Las expresiones del lenguaje incluyen llamadas a las operaciones de los tipos predefinidos, accesos a los atributos y
operaciones de los tipos/clases de usuario, y navegación a través de asociaciones. Dado un objeto es posible acceder
a sus atributos o invocar sus operaciones mediante el operador punto (.) o, en el caso de que el objeto sea una
colección, el operador flecha (->). El operador punto permite además navegar a través de las asociaciones, indicando
el nombre del extremo opuesto de la asociación. En el caso más general, la navegación dará lugar a un Set. Con OCL
podemos especificar invariantes de clases, que son expresiones lógicas que han de cumplirse durante toda la vida de
las instancias de dichas clases, y especificaciones pre/post de operaciones. Consideremos la invariante para la clase
Departamento en el modelo ejemplo:

Departamento
------------
profesores->exists (prof | prof = self.director)

La expresión es cierta cuando en el conjunto de instancias que resulta al navegar hacia el extremo profesores de la
clase Departamento, existe un profesor que es el mismo que el que se obtiene al navegar hacia el extremo director.
Figura 2: Metamodelo UML (Subpaquete Core – parcial)

3. El metamodelo UML

El metamodelo UML [UML972] está organizado en tres paquetes: Foundation (que contiene los constructores de la
parte estructural de un modelo), Behavioral Elements (que encapsula los constructores de las partes funcional y
dinámica) y Model Management (que contiene constructores para la organización lógica y física de los elementos de
un modelo). La visión funcional la tenemos cubierta con el lenguaje de especificación OCL, y la visión dinámica
queda para un trabajo futuro, por lo que prescindiremos de Behavioral Elements. Foundation está estructurado en
cuatro subpaquetes: Core (donde se encuentran los constructores básicos), Auxiliary Elements (constructores que
complementan a los anteriores), Extension Mechanisms (constructores para extender UML) y Data Types (tipos de
datos necesarios para definir UML). La parte fundamental del paquete Core se presenta en la figura 2. El criterio
para interpretar el metamodelo consiste en recorrer el árbol jerárquico, partiendo de las hojas (que siempre son
metaclases no abstractas y, por tanto, sus instancias son elementos en un modelo de clases) y subiendo hacia la raíz.
En cada nivel hay que considerar los metatributos y navegar las metaasociaciones, completando de esta manera la
información sobre las metaclases de partida. Por ejemplo, Interfaz, Clase y TipoDeDato (cuyas instancias son las
interfaces, clases y tipos de datos en cualquier modelo UML) son subclases directas de Clasificador, y por ello son
elementos generalizables (metaclase ElementoGeneralizable), sirven de espacio de nombres para otros elementos
(metaclase EspacioDeNombres), tienen un nombre que las identifica (posiblemente dentro de un espacioDeNombres,
metaclase ElementoDeModelo), pueden tener restricciones (metarole restriccion de ElementoDeModelo), pueden
tener dependencias con otros elementos (no aparece en la figura), pueden poseer atributos, operaciones y métodos
(agregación exclusiva de Clasificador a Caracteristica, de la que cuelgan Atributo, Operacion y Metodo) y sirven de
extremos para las posibles asociaciones (no se muestra en el diagrama).

4. Mecanismo de traducción

Partimos de una representación del modelo en formato de texto, los ficheros con extensión mdl de la herramienta
Rational Rose. La traducción consta de cinco fases en cascada.

• Fase 1: Análisis léxico-sintáctico del modelo


ü Entrada: fichero de texto que representa el modelo UML/OCL.
ü Proceso: realizar un análisis léxico-sintáctico del fichero de texto, e instanciar las metaclases UML que
representen cada elemento de modelo que se vaya reconociendo. Si estas nuevas instancias están
relacionadas con otras ya creadas , se establecerán los respectivos enlaces.
ü Salida : grafo de instancias de metaclases UML.
ü Comentarios:
1) Para poder crear las instancias es necesario disponer de una representación del metamodelo en forma de
biblioteca de clases. No nos centraremos en los detalles de implementación de dicha biblioteca.
2) Para ilustrar el proceso, pongamos dos ejemplos. Si lo que se ha reconocido en el fichero es un atributo
de clase, se instanciará la metaclase Atributo y se copiará en dicho objeto la información relativa al
atributo. En el caso de haber reconocido una clase, se creará una instancia de Clase y se enlazará con
todos los objetos ya creados con los que tenga alguna relación (objetos Atributo, Operacion,
Asociacion, ...; véase la figura 2).
3) En esta fase se evitará el análisis léxico-sintáctico de las expresiones OCL subsumidas en el fichero
".mdl". Por cada una se creará una instancia de la metaclase Restriccion, en cuyo metatributo cuerpo se
incluirá el texto de la expresión. Si la expresión describe el valor inicial de un atributo, se enlazará
dicha instancia de Restriccion con la instancia de Atributo que represente al atributo. Análogamente, si
la expresión es una invariante de clase, se enlazará la instancia de Restriccion con la correspondiente de
Clase. Por último, si se trata de una pre/postcondición de una operación, se enlazará con la respectiva
instancia de Operacion.
• Fase 2: Simplificación del grafo
ü Entrada: grafo de instancias de metaclases UML.
ü Proceso: recorrer el grafo buscando objetos temporales; por cada uno, hallar en el grafo el objeto real,
borrar el temporal y actualizar los enlaces, si los hubiere.
ü Salida : grafo simplificado de instancias de metaclases UML.
ü Comentarios : la conveniencia de realizar esta fase depende fuertemente de la implementación de la
construcción del grafo en la fase anterior. Con un diseño cuidadoso de dicha fase evitaríamos la creación de
objetos temporales en el grafo y, por tanto, su posterior simplificación.

• Fase 3: Análisis léxico-sintáctico de las expresiones OCL


ü Entrada: grafo simplificado de instancias de metaclases UML.
ü Proceso: por cada objeto Restriccion del grafo, realizar un análisis léxico-sintáctico de su expresión OCL.
El análisis debe ir construyendo el árbol de sintaxis abstracta correspondiente a la expresión. Si ésta no
contiene errores léxico-sintácticos, el objeto árbol se guardará como atributo del objeto Restriccion que
representa la expresión OCL.
ü Salida : grafo de instancias enriquecido con árboles de sintaxis abstracta.
ü Comentarios : si al acabar el recorrido del grafo se han detectado errores léxico-sintácticos, se mostrarán y
no se pasará a la siguiente fase.

• Fase 4: Análisis semántico de las expresiones OCL


ü Entrada: grafo de instancias enriquecido con árboles de sintaxis abstracta.
ü Proceso: por cada objeto Restriccion del grafo, realizar un análisis semántico de su atributo árbol de
sintaxis abstracta.
ü Salida: grafo de instancias enriquecido con árboles de sintaxis abstracta.
ü Comentarios:
1) El análisis semántico consiste en verificar la conformidad de tipos y que los accesos a atributos y
llamadas a operaciones sean correctos. Cada evaluación dará como resultado el tipo de la expresión, en
forma de un objeto Clasificador que estará en el grafo (si se trata de un tipo definido por el usuario) o
bien se encontrará entre los predefinidos por OCL. Los tipos OCL se consideran instancias de la
metaclase TipoDeDato en el metamodelo UML, ya que son tipos puros. Las evaluaciones de sus
términos no alterarán el estado del modelo.
2) Si al acabar el recorrido del grafo se han detectado errores semánticos, se mostrarán y no se pasará a la
siguiente fase.
• Fase 5: Generación de código Java
ü Entrada: grafo de instancias enriquecido con árboles de sintaxis abstracta de la fase anterior.
ü Proceso: recorrer el grafo, comenzando por el objeto que representa el paquete raíz del modelo, y generar
código para cada uno de sus elementos.
ü Salida: un fichero con extensión “.java” por cada clase del modelo.
ü Comentarios :
1) Generar código Java para un objeto Paquete consiste en generar código recursivamente para sus
contenidos. Además hay que crear un directorio con el mismo nombre que el paquete en el que se irán
guardando los ficheros correspondientes al código de los objetos Clase que contenga. La jerarquía de
directorios debe ir pareja a la jerarquía de paquetes del modelo.
2) Cada objeto Clase se traduce directamente por una clase Java, con los mismos atributos y el mismo
perfil para sus operaciones.
3) Cada objeto ExtremoDeAsociacion que sea navegable se genera como un atributo nuevo en la clase
opuesta. Si su multiplicidad es 1, el tipo del atributo ser la clase a la que hace referencia el extremo. Si
no, será el tipo OCL Sequence o Set, dependiendo de que aparezca la restricción {ordenado} o no junto
al extremo.
4) La representación Java de los tipos básicos de OCL es inmediata: Integer da lugar a long, Real a
double, Boolean a boolean, y String a la clase String del paquete java.lang. Las operaciones
predefinidas son soportadas por dichos tipos, necesitando tan sólo algunos retoques en el caso de String.
5) Los tipos de colección de OCL no aparecen directamente en Java, por lo que hemos escrito un paquete
ocl en el que están definidas la interfaz Collection y las clases Set, Bag y Sequence. Las dos primeras
son subclases de Hashtable , mientras que Sequence es subclase de Vector, ambas definidas en java.util
[Ja97]. Desde dichas clases podemos heredar todas las operaciones de los respectivos tipos OCL
excepto los cuantificadores (exists y forAll) y los filtros (collect, select, reject e iterate). Tales
operaciones están parametrizadas con el tipo de la colección sobre la que se aplican y con una expresión
booleana (en caso de forAll, exists, select y reject) o de cualquier tipo (collect e iterate). Hay dos
opciones para implementarlas: A) o hacemos genérico el código Java para filtros y cuantificadores (y
para ello habría que hacer uso de las interfaces de Java) o B) creamos, por cada aparición de un filtro o
un cuantificador en una expresión OCL, una función miembro de la clase a la que esa expresión está
adscrita, con un código que resuelva el caso particular de dicha aparición; por ejemplo, si el filtro o el
cuantificador estuviese en una pre/postcondición de una operación op, se generaría para el mismo una
función privada fc adscrita a la clase que contiene a op, junto con una llamada a fc desde el código de
op; si estuviese en una invariante in de clase, fc se generaría adscrita a dicha clase, junto con la llamada
a fc desde in. Nos parece mejor la opción A, aunque por rapidez en obtener un prototipo hemos
implementado la B.
6) Las restricciones OCL (invariantes y pre/postcondiciones de operaciones) tienen una interpretación
funcional. Su traducción a código Java se realiza mediante funciones miembros de las clases
correspondientes. El código se obtiene mediante composición del código generado para las operaciones
de los tipos que aparecen en la restricción.
7) Como primera aproximación para generar código para una operación de una clase C dada por su
pre/postcondición, y siendo conscientes de sus limitaciones, hemos optado por el siguiente esquema
general, que se instanciaría dentro del código de la clase C:
<perfil de la operación>
{
Boolean b;
<código para la precondición; el resultado se devuelve en b>
if (b){
<código para la postcondición>
}
}
8) También como primera aproximación generamos las invariantes de una clase C uniéndolas mediante
conjunciones para obtener una única expresión, y a continuación se genera la misma en una función
pública adscrita a C que devuelve un valor lógico indicando si las invariantes se cumplen para el objeto
actual en el estado actual del sistema:
public boolean invariante ()
Por ejemplo, consideremos de nuevo la invariante de la clase Departamento:

Departamento
------------
profesores->exists (prof | prof = self.director)

El código se genera como una función pública miembro de la clase java Departamento de la siguiente forma:
1) Resolución de la navegación a través del extremo profesores.
2) Llamada a la función miembro auxiliar que simulará la aparición del filtro exists (función iterate4).
3) Composición final del resultado.
Además:
4) Generar código para el filtro en la función miembro privada iterate4. Para ello importamos el esquema
genérico de implementación del exists y generamos código para la navegación hacia el extremo
director y para la comparación, lo cual es inmediato.
El código final quedaría:
public boolean invariante ()
{
boolean result; //resultado de la invariante
boolean x1;
Set x2;
x2= profesores; //Paso 1
boolean x9;
x9= iterate4(x2); //Paso 2
x1= x9;
result= x1; //Paso 3
return result;
}

boolean iterate4 (Set coleccion)


//Paso 4. Implementacion de la expresion exists
{
Profesor x3; //declaracion del iterador
if (coleccion.puedoLeer()){
x3= ((Profesor)coleccion.leer());
boolean x5;
Profesor x7;
Departamento x8;
x8= this;
x7= x8.director;
boolean x6;
if (x3.equals (x7))
x6= true;
else
x6= false;
x5= x6;
if (x5)
return true;
return (iterate4 ((Set)coleccion.resto()));
}
return false;
}
5. Trabajos relacionados

El desarrollo formal de programas desde especificaciones es un tema ampliamente tratado a nivel de programación a
pequeña escala (por ejemplo, [Dro89], [Jac93], [Par90]). Éstos hacen hincapié principalmente en mostrar sólo la
existencia de leyes rigurosas en la construcción de programas pero no se centran en la viabilidad de las mismas a la
hora de su posible aplicación real y a una escala mayor (posiblemente en un entorno industrial). La orientación a
objetos también desplaza la atención de la síntesis clásica de programas. Hasta ahora, la mayor parte de los trabajos
de síntesis se centraban en la programación a pequeña escala y sin considerar los conceptos de orientación a objetos.
La necesidad de probar la equivalencia semántica entre especificación y programa nos lleva a adoptar algún tipo de
demostrador (por ejemplo, el proceso de obligación de prueba en VDM, paradigma de prueba como programa
[ConBat85], etc.) asociado al proceso de síntesis sin el cual no podríamos asegurar la validez de la transformación.
La ampliación del lenguaje de especificación con conceptos orientados a objetos nos lleva a un proceso de síntesis
más complejo (subsumiendo la situación anterior). La efectividad en la síntesis de código y su aplicación a gran
escala nos obliga a replantearnos las exigencias de pruebas necesarias en toda transformación y abordar el problema
no sólo desde la perspectiva clásica de los demostradores de teoremas generalistas, sino además, valiéndonos de la
experiencia en problemas bien conocidos y recurrentes y adoptando las soluciones a los mismos como punto de
partida que nos evitará, en parte, el "cuello de botella" que representa la aproximación clásica. El estudio de los
problemas y una metodología de especificación de los mismos nos ayudará a condicionar mejor el proceso de síntesis
evitándonos un gran trabajo de demostración.

6. Conclusiones y Trabajo Futuro

En esta primera etapa del trabajo se ha resuelto:


1) Un traductor que transforma modelos de clases de sistemas enriquecidos con restricciones OCL a programas
Java que los representan.
2) El proceso es completamente automático, no hay interacción con el usuario.
3) La corrección total de la traducción queda asegurada debido a la corrección de los pasos aplicados y del código
java que implementa los tipos de datos predefinidos para OCL, así como a la suposición de que los
cuantificadores universal y existencial trabajan sobre conjuntos finitos.
4) Extensión en la generación de código de una herramienta comercial. Creemos interesante cubrir este aspecto
siempre deficiente en las herramientas CASE.
Como trabajo futuro queda pendiente:
1) Elaborar la interfaz gráfica de modelado UML/OCL, y con ello independizarnos de Rational Rose.
2) Cubrir los modelos dinámicos, por ejemplo, diagramas de colaboración y diagramas de transición de estados.
3) Proponer de manera automática al usuario diagramas de transición estados a partir de diagramas de actividad
diseñados por el propio usuario.
4) Tratar la implementación del control en el sistema. Dependiendo del tipo de implementación, nos hará falta la
generación de clases transparentes al usuario que monitoricen dicho control.
5) Potenciar el lenguaje de restricciones con definiciones de un nivel de abstracción más alto.
6) Potenciar el traductor para hacer frente a especificaciones más complejas.
7) Dar al usuario la posibilidad de modelar mediante patrones de diseño, y generar código para los mismos.
8) Generar código en lenguajes distintos a Java (Ada95, C++, ...). También consideramos de gran interés la
generación automática de partes distintas del mismo sistema en lenguajes distintos, dependiendo de la
conveniencia en cada caso (por ejemplo, Java para el núcleo del sistema, C++ para las capas de interacción con
el hardware y SQL para la comunicación con bases de datos).
9) Optimizar el código final obtenido.

7. Referencias

[CD94] S. Cook, J. Daniels. Designing Object Systems. Object-Oriented Modelling with Syntropy. Prentice Hall
1994.
[ConBat85] R.L. Constable and J.L. Bates. Programs as Proofs. Dept. of Computer Science, Cornell University,
November 1992. ACM Transactions on Programming Languages and Systems, Vol 7, Nº 1,1985
[Dro89] G. Dromey. Program Derivation. Addison-Wesley, 1989.
[Jac93] J. M. Jacquet. Constructing Logic Programs. J. Wiley and Sons, 1993.
[Ja97] J. Jaworski. Java: Guía de desarrollo. Prentice Hall 1997.
[OCL97] Rational Software, Microsoft y otros. Object Constraint Language Specification. Versión 1.1. Septiembre
1997.
[Par90] H.A. Partsch. Specification and Transformation of Programs. Springer Verlag, 1990.
[UML971] Rational Software, Microsoft y otros. Notation Guide. Versión 1.1. Septiembre 1997.
[UML972] Rational Software, Microsoft y otros. UML Semantics. Versi¢n 1.1. Septiembre 1997.

También podría gustarte