Um Guia Completo sobre a Linguagem de Restrição de Objetos (OCL)
Com base nos materiais de aula fornecidos, este guia aborda os conceitos fundamentais, a sintaxe e as aplicações práticas da Linguagem de Restrição de Objetos (OCL) no contexto da modelagem UML.

1. Introdução ao OCL
Por que precisamos do OCL?
Embora os diagramas UML (como os diagramas de classes) sejam excelentes para visualizar estruturas, eles não conseguem expressar todos os aspectos relevantes de uma especificação.
-
Linguagem natural é ambígua.
-
Linguagens formais tradicionais são inequívocas, mas muito difíceis de ler e escrever para modeladores médios de sistemas.
-
OCL fecha essa lacuna: é uma linguagem formal usada para expressar restrições precisas, mas ainda permanece fácil de ler e escrever.
Características Principais do OCL
-
Linguagem de Expressão Pura: As expressões não têm efeitos colaterais. Avaliar uma expressão OCL retorna um valor, mas não pode alterar o estado do sistema. No entanto, pode descrever uma mudança de estado (por exemplo, em uma pós-condição).
-
Não é uma Linguagem de Programação: Você não pode escrever lógica de programa, fluxo de controle ou invocar processos/operacões que alterem o estado.
-
Linguagem Tipada: Toda expressão tem um tipo. As expressões bem formadas devem obedecer às regras de conformidade de tipo. Todo classificador UML é um tipo OCL.
-
Avaliação Instantânea: O estado dos objetos não pode mudar durante a avaliação de uma expressão OCL.
2. Sintaxe e Semântica Básicas
Contexto e Self
Toda expressão OCL é avaliada no contexto de um classificador UML específico (classe, interface, etc.).
-
contexto: Define a classe à qual a restrição se aplica. -
self: Uma palavra reservada que se refere à instância específica da classe de contexto sendo avaliada.
contexto Pessoa
-- 'self' se refere a uma instância específica de Pessoa
Comentários e Operadores
-
Comentários: Denotado por
--(por exemplo,-- este é um comentário). -
Operadores Infixos: Você pode usar operadores matemáticos/lógicos padrão (
+,-,=,<). Conceitualmente,a + bé idêntico aa.+(b).
Acessando Propriedades
Use a notação de ponto para acessar atributos ou operações. Parênteses são obrigatórios para operações, mesmo que não tenham parâmetros.
contexto Pessoa
self.idade -- Acessa o atributo 'idade'
self.precoAcao() -- Chama a operação 'precoAcao'
3. Tipos e Coleções
Tipos Pré-definidos Básicos
OCL inclui tipos padrão com operações específicas:
-
Booleano:
verdadeiro,falso(Operações:e,ou,não,implica,ou exclusivo) -
Inteiro / Real: Números (Operações:
+,-,*,/,abs,max,min,div,mod) -
String: Texto (Operações:
tamanho,concat,substring,paraInteiro)
Coleções
Coleções são tipos abstratos usados para armazenar múltiplos elementos. São acessados usando o -> notação.
-
Conjunto: Conjunto matemático (sem duplicatas, desordenado).
Conjunto {1, 2, 5} -
ConjuntoOrdenado: Sem duplicatas, mas ordenado pela posição.
ConjuntoOrdenado {5, 4, 3} -
Bolsa: Permite duplicatas, não ordenado.
Bolsa {1, 2, 2, 5} -
Sequência: Permite duplicatas, ordenado.
Sequência {1, 2, 5, 10}-
Observação: O
..A notação cria uma sequência de inteiros consecutivos:Sequência {1..5}.
-
Operações Principais de Coleção
-
Comuns:
tamanho(),estaVazio(),naoEstaVazio(),inclui(v),exclui(v),conta(v),soma(). -
Iteradores (Cruciais para OCL):
-
seleciona(expr): Retorna elementos ondeexpré verdadeiro. -
reject(expr): Retorna elementos ondeexpré falso. -
collect(expr): Extrai uma propriedade específica de todos os elementos para uma nova coleção. -
forAll(expr): Retornaverdadeiroseexpré verdadeiro para todos elementos. -
exists(expr): Retornaverdadeiroseexpré verdadeiro para pelo menos um elemento. -
iterate(elem; acc = init | expr): A base genérica para todos os iteradores.
-
Variações de sintaxe do iterador:
self.employee->select(idade > 50)
self.employee->select(p | p.idade > 50)
self.employee->select(p: Pessoa | p.idade > 50)
OCL é amplamente utilizado como uma linguagem de navegação para percorrer associações.
Navegue usando o nome do papel oposto. O tipo de retorno depende da multiplicidade:
-
Multiplicidade
1ou0..1: Retorna um único objeto. -
Multiplicidade
*: Retorna umConjunto(ouConjuntoOrdenadose marcado{ordenado}).
contexto Empresa
self.gerente.idade > 40 -- gerente é 0..1, retorna um objeto Pessoa
self.funcionario->naoVazio() -- funcionário é 0..*, retorna um Conjunto
Se um nome de papel estiver ausente, use o nome em minúsculas da classe na outra extremidade (por exemplo, self.banco).
Classes de Associação
Navegue até uma classe de associação usando o nome em minúsculas da classe de associação.
contexto Pessoa
self.trabalho->tamanho() >= 1 -- 'trabalho' é a classe de associação entre Pessoa e Empresa
Associações Recursivas
Quando uma associação volta para a mesma classe (por exemplo, Casamento entre duas Pessoas), você deve especificar a direção usando colchetes [nomePapel].
contexto Pessoa
self.marriage[esposa]->select(m | m.terminado = false)->size() = 1
Associações Qualificadas
Use atributos qualificadores entre colchetes para selecionar um objeto específico.
contexto Banco
self.cliente[12345] -- Retorna a Pessoa específica com accountNo 12345
self.cliente -- Retorna um Conjunto de todos os clientes
Re-tipagem (Casting)
Use oclAsType() para acessar propriedades de um subtipo ou uma propriedade oculta da superclasse.
self.oclAsType(Sub).p -- Acessa a propriedade 'p' definida no Subtipo
self.oclAsType(Super).p -- Acessa a propriedade 'p' definida na Superclasse
5. Escrevendo Restrições
1. Invariantes (inv)
Condições que devem sempre ser verdadeiras para todas as instâncias de uma classe.
contexto Empresa
inv: self.numeroFuncionarios <= 50
inv PME: self.precoAcao() > 0 -- 'PME' é um nome opcional para a restrição
2. Pré e Pós-condições (pré, pós)
Restrições vinculadas a operações.
-
pré: Deve ser verdadeiro antes da execução. -
pós: Deve ser verdadeiro depois execução. Useresultadopara o valor de retorno. -
@pre: Usado em pós-condições para referenciar o valor de uma propriedade antes a operação começou.
contexto Pessoa::renda(): Inteiro
pré adulto: self.idade >= 18
pós resultadoOK: resultado < 5000
contexto Empresa::contratarFuncionario(p: Pessoa)
pós: funcionario = funcionario@pre->incluindo(p)
3. Expressões Body, Inicial e Derivada
-
body: Define o resultado de uma operação de consulta (pode ser recursiva). -
init: Define o valor inicial de um atributo. -
derive: Define um atributo derivado.
contexto Pessoa::renda(): Inteiro
body: self.trabalho.salario->soma()
contexto Pessoa::casado: Boolean
init: falso
contexto Empresa::numeroFuncionarios: Inteiro
derive: self.funcionario->tamanho()
6. Recursos Avançados do OCL
Expressões Let e Def
-
let: Define uma variável local dentro de uma única expressão.let totHoras: Inteiro = self.trabalhaEm->coletar(horas)->soma() em totHoras >= 30 -
def: Define variáveis ou operações reutilizáveis associadas a um classificador.def: temTitulo(t: String): Boolean = self.trabalho->existe(titulo = t)
Valores Indefinidos (Lógica de 3 Valores)
Se uma sub-expressão é indefinida (por exemplo, navegar uma associação nula), toda a expressão é indefinida. No entanto, os operadores booleanos têm exceções de curto-circuito:
-
verdadeiro ou <indefinido>= verdadeiro -
falso e <indefinido>= falso -
falso implica <undefined>= verdadeiro -
<undefined> implica verdadeiro= verdadeiro
Recursos da Classe e Propriedades de Objetos Pré-definidos
-
allInstances(): Retorna um Conjunto de todas as instâncias existentes de uma classe.Person.allInstances()->size() <= 100 -
Propriedades de objeto:
oclIsTypeOf(Tipo),oclIsKindOf(Tipo),oclInState(Estado),oclIsNew(usado em pós-condições).
7. Estudo de Caso Compreensivo: O Modelo da Empresa
Para consolidar esses conceitos, aqui está uma coleção de restrições de integridade aplicadas a um modelo de banco de dados da Empresa (Funcionários, Departamentos, Projetos, Dependentes).
Restrições Básicas de Atributos
-- A idade deve ser >= 18
contexto Funcionário inv: self.idade() >= 18
-- A data de contratação deve ser posterior à data de nascimento
contexto Funcionário inv: self.dataContratacao > self.dataNascimento
-- A data de início do gerente deve ser posterior à data de contratação
contexto Funcionário inv: self.gerencia->nãoVazio() implica self.gerencia.dataInicio > self.dataContratacao
-- O supervisor deve ser mais velho e ter salário maior que o funcionário
contexto Funcionário
inv: self.supervisor->nãoVazio() implica self.idade() > self.supervisor.idade()
inv: self.supervisor->nãoVazio() implica self.salario < self.supervisor.salario
-- Um supervisor deve ser contratado antes de seus subordinados
contexto Funcionário
inv: self.subordinados->nãoVazio() implica self.subordinados->paraTodos(e | e.dataContratacao > self.dataContratacao)
-- O gerente do departamento deve ser funcionário desse departamento
contexto Departamento
inv: self.trabalhaPara->inclui(self.gerencia.funcionario)
-- A localização do projeto deve ser uma das localizações do departamento
contexto Projeto
inv: self.controla.localizacoes->inclui(self.localizacao)
-- Um funcionário só pode trabalhar em projetos controlados pelo seu departamento
contexto Funcionário
inv: self.trabalhaPara.controla->incluiTodos(self.trabalhaEm.projeto)
Restrições de Coleção e Iterador
-- O número de seguro social deve ser um identificador único entre todos os funcionários
contexto Funcionário
inv: Employee.allInstances->paraTodos(e1, e2 | e1 <> e2 implica e1.SSN <> e2.SSN)
-- O nome e o relacionamento do dependente devem ser únicos por funcionário
contexto Funcionário
inv: self.dependentes->paraTodos(e1, e2 |
e1 <> e2 implica (e1.nome <> e2.nome ou e1.relacionamento <> e2.relacionamento))
-- Atributo derivado: número de funcionários em um departamento
contexto Departamento
inv: self.nbrFuncionarios = self.trabalhaPara->tamanho()
-- Um funcionário trabalha em no máximo 4 projetos
contexto Funcionário
inv: self.trabalhaEm->tamanho() <= 4
Lógica Complexa usando deixe e selecione
-- As horas trabalhadas totais devem estar entre 30 e 50 por semana
contexto Funcionário
inv: deixe totHoras: Inteiro = self.worksOn->coletar(horas)->soma()
em totHoras >= 30 e totHoras <= 50
-- No máximo 2 funcionários podem trabalhar menos de 10 horas em um projeto específico
contexto Projeto
inv: self.worksOn->selecionar(horas < 10)->tamanho() <= 2
-- Apenas gerentes de departamento podem trabalhar menos de 5 horas em um projeto
contexto Funcionário
inv: self.worksOn->selecionar(horas < 5)->nãoVazio() implica
self.worksFor.manages.funcionário = self
-- Funcionários sem subordinados devem trabalhar >= 10 horas em TODOS os seus projetos
contexto Funcionário
inv: self.subordinates->vazio() implica self.worksOn->paraTodos(horas >= 10)
-- O gerente do departamento deve trabalhar >= 5 horas em todos os projetos controlados pelo departamento
contexto Departamento
inv: self.controls->paraTodos(p: Projeto |
self.manages.funcionário.worksOn->selecionar(horas >= 5)->contém(p))
Evitando Ciclos (Lógica Recursiva)
-- Um funcionário não pode supervisionar a si mesmo
contexto Funcionário inv: self.subordinates->exclui(self)
-- A hierarquia de supervisão não pode ser cíclica
contexto Funcionário
def: todosSubordinados = self.subordinates->união(
self.subordinates->coletar(e: Funcionário | e.todosSubordinados))
inv: self.todosSubordinados->exclui(self)














