Refinando UML: Object Constraint Language

Muchas veces nos vemos en la situación de que queremos expresar condiciones o restricciones sobre un modelo (como por ejemplo, un diagrama de clases), pero vemos que no es posible hacerlo con las herramientas que nos provee UML.

Tomemos el siguiente ejemplo. Un extracto de un modelo para un dominio particular, pero conocido en lo general:

Untitled

Podemos decir en base a este diagrama que una Factura está compuesta por Items, y que además tiene otros atributos, como ser la fecha, el número de orden de compra y el número de factura. También vemos un atributo derivado (cuyo valor será inferido), el cual se identifica con /numeroItems. Asimismo la factura tiene un método que se llama agregarItem(Item). Dentro de cada Item podemos encontrar el producto comprado, el precio unitario, la cantidad comprada.

Ahora bien, supongamos que tenemos la siguiente regla de negocio: «No puede haber, en una misma Factura, dos Items que referencien al mismo producto vendido».

Como vemos, no tenemos forma de especificar esto en el modelo utilizando UML. Es aquí donde aparece OCL.

Object Constraint Language – OCL

OCL es un lenguaje formal utilizado para especificar restricciones, entre otras cosas. Este lenguaje está basado en lógica de primer orden, por lo que nos permite eliminar las ambigüedades y así describir las reglas que deban acompañar nuestro modelo.

La idea es escribir condiciones invariantes que deben verificarse (deben ser verdaderas en todo momento) en el estado de las entidades del dominio que nuestro sistema maneja.

Cada invariante de OCL está definida en un Contexto, que es la clase en la cual se va a aplicar la misma.  Dentro de su especificación, vamos a poder navegar por el modelo, basándonos en las relaciones que haya en él (utilizando la notación de punto, y los nombres de los roles, para las relaciones).

El lenguaje hace uso de la teoría de conjuntos, por ende podemos especificar nuestras invariantes utilizando operaciones aplicables en conjuntos. Podemos iterar elementos de una colección, seleccionar algunos en base a una determinada condición, etc.

La definición concreta del lenguaje excede el alcance de este post, pero vamos a ver la resolución del ejemplo mencionado anteriormente.

El primer paso es definir el contexto. Vamos a aplicar la invariante a la clase Factura. Las invariantes pueden tener un nombre, el cual nos ayudará a saber que regla no se está cumpliendo, al momento de validar el dominio. Para nuestro caso, la llamaremos «dosItemsConElMismoProducto«.

El siguiente paso es definir la regla en sí:

[code]
context Factura inv dosItemsConElMismoProducto:
self.items->size() = self.items.producto->asSet()->size()
[/code]

self es una palabra propia del lenguaje, que hace referencia a la clase del contexto, en nuestro ejemplo, es la Factura. Vemos que mediante una notación de puntos podemos navegar entre los atributos y relaciones de las clases, por lo que self.items nos retorna un conjunto (por la cardinalidad de la relación de composición). La operación asSet() se aplica a un conjunto generalmente para eliminar los elementos duplicados. Esta invariante entonces verifica que la cantidad de items de una factura sea la misma que la cantidad de productos vendidos. De forma que si hay un item que tenga un producto repetido, la cantidad de productos será menor a la cantidad de items, y la invariante no se cumplirá, violando la restricción.

Operaciones y Reglas de derivación

Además de permitirnos agregar restricciones, también podemos especificar operaciones, consultas y reglas de derivación.

Al especificar una operación tenemos la posibilidad de agregar, con un alto nivel de abstracción y lejos de un lenguaje de programación determinado, la forma en que una operación va a dejar los objetos del sistema. Además podemos definir las precondiciones que se deben cumplir para poder llevar a cabo una operación dada. Tomemos como ejemplo la operacion agregarItem(Item) perteneciente a la factura.

[code]
context Factura::agregarItem(Item i):Item
post:
result.oclIsNew() and
result.producto = i.producto and
result.precioUnitario = i.precioUnitario and
result.cantidad = i.cantidad and
self.items = self.items@pre->including(result)
[/code]

result es una palabra reservada de OCL que puede utilizarse en las post-condiciones de las operaciones. El contexto define primero la clase (Factura), luego la operacion, y luego el tipo de retorno. Implícitamente, result toma el tipo que definimos para el retorno (Item). El operador @pre en este caso es aplicado a los items de una factura, y nos retorna el estado de los mismos antes de aplicar la operación. including es un método aplicado a conjuntos, y evalúa a verdadero si el conjunto determinado contiene el elemento pasado como parámetro. Entonces nuestra post-condición dice que hay un elemento result que fue creado por la operación (oclIsNew() evalúa a verdadero en esta situación), que va a tener los mismos valores que el ítem que se pasa como parámetro, y que el conjunto de items va a contener el nuevo elemento creado.

Con OCL podemos también definir consultas. Estas consultas están lejos de especificar la implementación de las mismas, pero definen sin ambigüedad, los elementos que tienen que retornarse.

Para las reglas de derivación sucede lo mismo, especifican de manera unívoca los valores que debe tomar una propiedad específica. Veamos por ejemplo la regla de derivación para el campo númeroItems de la Factura.

[code]
context Factura::numeroItems:Integer derive:
self.items->size()
[/code]

Con esta regla de derivación estamos diciendo que el atributo numeroItems de la Factura, sera de tipo entero y tomará como valor la cantidad de elementos que tenga la lista de ítems presentes en la factura.

Agregando las restricciones al modelo

Hay distintas opciones para trabajar con OCL. Una de ellas, es agregar un documento de texto que contenga las reglas que hayamos escrito, para que acompañen el modelo. Algunas herramientas de modelado ya tienen incorporado el soporte, por ende nos permiten incorporar directamente en el modelo las restricciones que necesitemos. Enterprise Architect, por ejemplo, tiene soporte para OCL. A continuación se muestra una imagen donde se ve el cuadro de diálogo de EA donde se pueden agregar las restricciones OCL. eaocl

Ventajas del uso de OCL

Como conclusión, podemos decir que, si bien el el tiempo de modelado es mayor, el uso de OCL tiene algunas ventajas:

  • Evitar la ambigüedad
  • Aumenta la consistencia
  • Es un estándar
  • Ampliar el significado del modelo agregando aspectos que no pueden agregarse mediante UML.

Según mi opinión personal, OCL debe ser tomado como una herramienta más de modelado. No debemos incorporar OCL a todos los modelos, porque no todos los modelos necesitan mayor especificación. En cambio debemos saber de su existencia, para poder tenerlo presente cuando nos topemos con situaciones en las que agregar mayor información al modelo sirva para clarificar el mismo, y evitar posibles problemas más tarde en el proceso. Es clave también el hecho de que el modelo expresa más contenido con las reglas OCL, por ende, sin dudas, dejará menos aspectos librados a la interpretación de los participantes siguientes en el proceso de desarrollo del sistema.

La especificación de OCL puede encontarse aquí.

Tags

Access top talent now!

Related

Get in Touch