de_DEen_USes_ESfa_IRfr_FRhi_INid_IDjapl_PLpt_PTru_RUvizh_CNzh_TW

Basado en los materiales de la conferencia proporcionados, esta guía cubre los conceptos fundamentales, la sintaxis y las aplicaciones prácticas del Lenguaje de Restricciones de Objetos (OCL) en el contexto de la modelización UML.

Objct Constraint Language (OCL)


1. Introducción a OCL

¿Por qué necesitamos OCL?

Aunque los diagramas UML (como los diagramas de clases) son excelentes para visualizar la estructura, no pueden expresar todos los aspectos relevantes de una especificación.

  • Lenguaje natural es ambiguo.

  • Lenguajes formales tradicionales son precisos pero demasiado difíciles para que los modeladores promedio de sistemas los lean y escriban.

  • OCL cierra esta brecha: es un lenguaje formal utilizado para expresar restricciones precisas, aunque sigue siendo fácil de leer y escribir.

Características principales de OCL

  1. Lenguaje de expresiones puro: Las expresiones no tienen efectos secundarios. Evaluar una expresión OCL devuelve un valor pero no puede alterar el estado del sistema. Sin embargo, puede describir un cambio de estado (por ejemplo, en una postcondición).

  2. No es un lenguaje de programación: No puedes escribir lógica de programa, flujo de control ni invocar procesos/operaciones que alteren el estado.

  3. Lenguaje tipado: Toda expresión tiene un tipo. Las expresiones bien formadas deben obedecer las reglas de conformidad de tipos. Cada clasificador UML es un tipo OCL.

  4. Evaluación instantánea: El estado de los objetos no puede cambiar durante la evaluación de una expresión OCL.


2. Sintaxis y semántica básicas

Contexto y Self

Cada expresión de OCL se evalúa en el contexto de un clasificador UML específico (clase, interfaz, etc.).

  • contexto: Define la clase a la que se aplica la restricción.

  • self: Una palabra reservada que hace referencia a la instancia específica de la clase de contexto que se está evaluando.

contexto Persona
-- 'self' hace referencia a una instancia específica de Persona

Comentarios y operadores

  • Comentarios: Denotado por -- (por ejemplo, -- este es un comentario).

  • Operadores infijos: Puedes usar operadores matemáticos/lógicos estándar (+-=<). Conceptualmente, a + b es idéntico a a.+(b).

Acceso a propiedades

Utilice la notación con punto para acceder a atributos u operaciones. Los paréntesis son obligatorios para las operaciones, incluso si no tienen parámetros.

contexto Persona
self.edad             -- Accede al atributo 'edad'
self.precioAcciones()    -- Llama a la operación 'precioAcciones'

3. Tipos y colecciones

Tipos predefinidos básicos

OCL incluye tipos estándar con operaciones específicas:

  • Booleano: verdaderofalso (Operaciones: yonoimplicaxor)

  • Entero / Real: Números (Operaciones: +-*/absmaxmindivmod)

  • Cadena: Texto (Operaciones: tamañoconcatsubcadenaaEntero)

Colecciones

Las colecciones son tipos abstractos utilizados para almacenar múltiples elementos. Se acceden mediante la -> notación.

  1. Conjunto: Conjunto matemático (sin duplicados, desordenado). Conjunto {1, 2, 5}

  2. ConjuntoOrdenado: Sin duplicados, pero ordenado por posición. ConjuntoOrdenado {5, 4, 3}

  3. Bolsa: Permite duplicados, desordenado. Bolsa {1, 2, 2, 5}

  4. Secuencia: Permite duplicados, ordenado. Secuencia {1, 2, 5, 10}

    • Nota: La .. La notación crea una secuencia de enteros consecutivos: Secuencia {1..5}.

Operaciones clave de colección

  • Comunes: tamano()esVacio()noEsVacio()incluye(v)excluye(v)cuenta(v)suma().

  • Iteradores (Cruciales para OCL):

    • seleccionar(expresión): Devuelve los elementos donde expr es verdadero.

    • reject(expr): Devuelve los elementos donde expr es falso.

    • collect(expr): Extrae una propiedad específica de todos los elementos en una nueva colección.

    • forAll(expr): Devuelve verdadero si expr es verdadero para todos elementos.

    • exists(expr): Devuelve verdadero si expr es verdadero para al menos uno elemento.

    • iterate(elem; acc = init | expr): La base genérica para todos los iteradores.

Variaciones de sintaxis del iterador:

self.employee->select(edad > 50)
self.employee->select(p | p.edad > 50)
self.employee->select(p: Persona | p.edad > 50)

4. Navegación de modelos UML

OCL se utiliza ampliamente como un lenguaje de navegación para recorrer asociaciones.

Navegación estándar

Navegue utilizando el nombre del rol opuesto. El tipo de retorno depende de la multiplicidad:

  • Multiplicidad 1 o 0..1: Devuelve un objeto único.

  • Multiplicidad *: Devuelve un Conjunto (o ConjuntoOrdenado si está marcado {ordenado}).

contexto Empresa
self.manager.edad > 40               -- manager es 0..1, devuelve un objeto Persona
self.empleado->noVacio()           -- employee es 0..*, devuelve un Conjunto

Si falta el nombre de un rol, utilice el nombre en minúsculas de la clase en el otro extremo (por ejemplo, self.banco).

Clases de asociación

Navegue hacia una clase de asociación utilizando el nombre en minúsculas de la clase de asociación.

contexto Persona
self.trabajo->tamano() >= 1  -- 'trabajo' es la clase de asociación entre Persona y Empresa

Asociaciones recursivas

Cuando una asociación vuelve sobre la misma clase (por ejemplo, matrimonio entre dos Personas), debe especificar la dirección utilizando corchetes [nombreRol].

contexto Persona
self.marriage[esposa]->select(m | m.terminado = false)->size() = 1

Asociaciones calificadas

Utilice los atributos calificadores entre corchetes para seleccionar un objeto específico.

contexto Banco
self.customer[12345]  -- Devuelve la Persona específica con accountNo 12345
self.customer         -- Devuelve un Conjunto de todos los clientes

Re-tipo (Casting)

Utilice oclAsType() para acceder a propiedades de un subtipo o a una propiedad oculta de la superclase.

self.oclAsType(Sub).p       -- Accede a la propiedad 'p' definida en el subtipo
self.oclAsType(Super).p     -- Accede a la propiedad 'p' definida en la superclase

5. Escritura de restricciones

1. Invariantes (inv)

Condiciones que deben siempre ser verdaderas para todas las instancias de una clase.

contexto Empresa 
inv: self.noEmployees <= 50
inv PYME: self.stockPrice() > 0  -- 'PYME' es un nombre opcional para la restricción

2. Precondiciones y poscondiciones (prepost)

Restricciones vinculadas a operaciones.

  • pre: Debe ser verdadero antes de la ejecución.

  • post: Debe ser verdadero después ejecución. Use resultado para el valor de retorno.

  • @pre: Utilizado en condiciones posteriores para referirse al valor de una propiedad antes cuando comenzó la operación.

contexto Persona::ingreso(): Entero
pre adulto: self.edad >= 18
post resultadoOK: resultado < 5000

contexto Empresa::contratarEmpleado(p: Persona)
post: empleado = empleado@pre->incluyendo(p)

3. Expresiones Body, Inicial y Derivada

  • body: Define el resultado de una operación de consulta (puede ser recursiva).

  • init: Define el valor inicial de un atributo.

  • derive: Define un atributo derivado.

contexto Persona::ingreso(): Entero
body: self.trabajo.sueldo->sum()

contexto Persona::esCasado: Boolean
init: falso

contexto Empresa::numEmpleados: Entero
derive: self.empleado->size()

6. Características avanzadas de OCL

Expresiones Let y Def

  • let: Define una variable local dentro de una sola expresión.

    let totHoras: Entero = self.trabajaEn->colectar(horas)->sum() en totHoras >= 30
    
  • def: Define variables o operaciones reutilizables asociadas a un clasificador.

    def: tieneTitulo(t: Cadena): Booleano = self.trabajo->existe(titulo = t)
    

Valores no definidos (lógica de 3 valores)

Si una subexpresión no está definida (por ejemplo, navegar una asociación nula), toda la expresión no está definida. Sin embargo, los operadores booleanos tienen excepciones de cortocircuito:

  • verdadero o <no definido> = verdadero

  • falso y <no definido> = falso

  • falso implica <undefined> = verdadero

  • <undefined> implica verdadero = verdadero

Características de clase y propiedades de objetos predefinidos

  • allInstances(): Devuelve un conjunto de todas las instancias existentes de una clase.

    Person.allInstances()->size() <= 100
    
  • Propiedades de objeto: oclIsTypeOf(Tipo)oclIsKindOf(Tipo)oclInState(Estado)oclIsNew (usado en condiciones posteriores).


7. Estudio de caso completo: El modelo de la empresa

Para consolidar estos conceptos, aquí hay una colección de restricciones de integridad aplicadas a un modelo de base de datos de empresa (Empleados, Departamentos, Proyectos, Dependientes).

Restricciones básicas de atributos

-- La edad debe ser >= 18
contexto Empleado inv: self.edad() >= 18

-- La fecha de contratación debe ser posterior a la fecha de nacimiento
contexto Empleado inv: self.fechaContratacion > self.fechaNacimiento

-- La fecha de inicio del gerente debe ser posterior a la fecha de contratación
contexto Empleado inv: self.gestiona->noVacio() implica self.gestiona.fechaInicio > self.fechaContratacion

Restricciones de navegación y relación

-- El supervisor debe ser mayor y tener un salario más alto que el empleado
contexto Empleado
inv: self.supervisor->noVacio() implica self.edad() > self.supervisor.edad()
inv: self.supervisor->noVacio() implica self.salario < self.supervisor.salario

-- Un supervisor debe haber sido contratado antes que sus subordinados
contexto Empleado
inv: self.subordinados->noVacio() implica self.subordinados->paraTodo(e | e.fechaContratacion > self.fechaContratacion)

-- El gerente del departamento debe ser un empleado de ese departamento
contexto Departamento
inv: self.trabajaPara->incluye(self.gestiona.empleado)

-- La ubicación del proyecto debe ser una de las ubicaciones del departamento
contexto Proyecto
inv: self.controla.ubicaciones->incluye(self.ubicacion)

-- Un empleado solo puede trabajar en proyectos controlados por su departamento
contexto Empleado
inv: self.trabajaPara.controla->incluyeTodos(self.trabajaEn.proyecto)

Restricciones de colección e iterador

-- El número de seguro social debe ser un identificador único entre todos los empleados
contexto Empleado
inv: Empleado.allInstances->paraTodo(e1, e2 | e1 <> e2 implica e1.SSN <> e2.SSN)

-- El nombre y la relación del dependiente deben ser únicos por empleado
contexto Empleado
inv: self.dependientes->paraTodo(e1, e2 |
      e1 <> e2 implica (e1.nombre <> e2.nombre o e1.relacion <> e2.relacion))

-- Atributo derivado: número de empleados en un departamento
contexto Departamento
inv: self.nbrEmpleados = self.trabajaPara->tamano()

-- Un empleado trabaja en como máximo 4 proyectos
contexto Empleado
inv: self.trabajaEn->tamano() <= 4

Lógica compleja usando dejar y seleccionar

-- Las horas totales de trabajo deben estar entre 30 y 50 por semana
contexto Empleado
inv: dejar totHoras: Entero = self.trabajaEn->recoger(horas)->suma()
     en totHoras >= 30 y totHoras <= 50

-- Como máximo 2 empleados pueden trabajar menos de 10 horas en un proyecto específico
contexto Proyecto
inv: self.trabajaEn->seleccionar(horas < 10)->tamaño() <= 2

-- Solo los gerentes de departamento pueden trabajar menos de 5 horas en un proyecto
contexto Empleado
inv: self.trabajaEn->seleccionar(horas < 5)->noVacio() implica
     self.trabajaPara.gestiona.empleado = self

-- Los empleados sin subordinados deben trabajar >= 10 horas en TODOS sus proyectos
contexto Empleado
inv: self.subordinados->estaVacio() implica self.trabajaEn->paraTodo(horas >= 10)

-- El gerente del departamento debe trabajar >= 5 horas en todos los proyectos controlados por el departamento
contexto Departamento
inv: self.controla->paraTodo(p: Proyecto |
      self.gestiona.empleado.trabajaEn->seleccionar(horas >= 5)->contiene(p))

Evitar ciclos (lógica recursiva)

-- Un empleado no puede supervisarse a sí mismo
contexto Empleado inv: self.subordinados->excluye(self)

-- La jerarquía de supervisión no debe ser cíclica
contexto Empleado
def: todosSubordinados = self.subordinados->union(
          self.subordinados->recoger(e: Empleado | e.todosSubordinados))
inv: self.todosSubordinados->excluye(self)