OCL (Object Constraint Language) y Especificación Formal
Los diagramas visuales de UML describen la estructura y las relaciones de un sistema, pero a menudo no pueden expresar con precisión ciertas reglas de negocio complejas, restricciones numéricas o condiciones de validez (por ejemplo: "el saldo nunca puede ser negativo", "un cliente debe ser mayor de edad" o "la fecha de inicio debe ser anterior a la de fin").
Para resolver esta limitación, el OMG estandarizó OCL (Object Constraint Language), un lenguaje formal, declarativo y libre de efectos secundarios (side-effect free) diseñado específicamente para enriquecer los modelos UML con restricciones matemáticas y de negocio precisas.
1. Características Principales de OCL #
- Declarativo y puro: No modifica el estado del sistema; evalúa expresiones que devuelven un valor (típicamente booleano).
- Fuertemente tipado: Posee tipos primitivos (
Boolean,Integer,Real,String), tipos del modelo UML (clases, interfaces) y tipos de colecciones (Set,Sequence,Bag,OrderedSet). - Independiente del lenguaje de programación: Se escribe a nivel de modelo y se puede traducir a Java, C#, TypeScript o SQL.
2. Tipos de Restricciones en OCL #
A. Invariantes (inv) #
Condiciones que deben cumplirse siempre en cualquier instancia de una clase antes y después de cualquier operación pública:
1context CuentaBancaria 2inv saldoValido: 3 self.saldo >= self.limiteDescubierto 4 5context Persona 6inv mayoriaEdad: 7 self.edad >= 18
B. Precondiciones (pre) y Postcondiciones (post) #
Definen el Diseño por Contrato (Design by Contract) para una operación:
- Precondición: Obligación del llamador. Debe ser verdadera antes de ejecutar el método.
- Postcondición: Garantía del método. Debe ser verdadera al terminar la ejecución. El sufijo
@predenota el valor del atributo antes de la ejecución.
1context CuentaBancaria::transferir(monto: Real, destino: CuentaBancaria) 2pre montoPositivo: 3 monto > 0 4pre saldoSuficiente: 5 self.saldo >= monto 6post saldoOrigenActualizado: 7 self.saldo = self.saldo@pre - monto 8post saldoDestinoActualizado: 9 destino.saldo = destino.saldo@pre + monto
3. Navegación entre Asociaciones y Colecciones #
En OCL, el operador punto (.) navega a atributos o asociaciones 1 a 1, mientras que el operador flecha (->) invoca operaciones sobre colecciones:
1@startuml 2class Empresa { 3 +nombre: String 4} 5 6class Empleado { 7 +nombre: String 8 +salario: Real 9 +activo: Boolean 10} 11 12Empresa "1" o-- "*" Empleado : empleados 13@enduml
Expresiones de Colecciones en OCL: #
1-- 1. Todos los empleados deben tener salario positivo 2context Empresa 3inv salarioPositivo: 4 self.empleados->forAll(e | e.salario > 0) 5 6-- 2. Debe haber al menos un empleado activo 7context Empresa 8inv existeEmpleadoActivo: 9 self.empleados->exists(e | e.activo = true) 10 11-- 3. Filtrar empleados activos con salario superior a 2000 12context Empresa::obtenerAltosCargos(): Set(Empleado) 13post: 14 result = self.empleados->select(e | e.activo and e.salario > 2000) 15 16-- 4. Obtener la suma total de la nómina 17context Empresa::totalNomina(): Real 18post: 19 result = self.empleados->collect(salario)->sum()
4. Traducción de OCL a Código de Producción #
5. Resumen de Operadores Clave en OCL #
| Operador OCL | Propósito | Ejemplo |
|---|---|---|
forAll(x | cond) | Verifica que todos los elementos cumplan la condición. | self.hijos->forAll(h | h.edad < 18) |
exists(x | cond) | Verifica que al menos un elemento cumpla la condición. | self.pedidos->exists(p | p.pagado) |
select(x | cond) | Filtra y devuelve el subconjunto de elementos que cumplen la condición. | self.usuarios->select(u | u.esAdmin) |
reject(x | cond) | Devuelve los elementos que NO cumplen la condición. | self.tareas->reject(t | t.completada) |
collect(expr) | Proyecta y transforma los elementos (análogo a map). | self.items->collect(precio) |
size() | Número de elementos de la colección. | self.empleados->size() > 0 |
includes(obj) | Comprueba si un objeto forma parte de la colección. | self.permisos->includes('ADMIN') |