de_DEen_USes_ESfa_IRfr_FRhi_INid_IDjapl_PLpt_PTru_RUvizh_CNzh_TW

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.

Objct Constraint Language (OCL)


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

  1. 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).

  2. 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.

  3. 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.

  4. 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 a a.+(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: verdadeirofalso (Operações: eounãoimplicaou exclusivo)

  • Inteiro / Real: Números (Operações: +-*/absmaxmindivmod)

  • String: Texto (Operações: tamanhoconcatsubstringparaInteiro)

Coleções

Coleções são tipos abstratos usados para armazenar múltiplos elementos. São acessados usando o -> notação.

  1. Conjunto: Conjunto matemático (sem duplicatas, desordenado). Conjunto {1, 2, 5}

  2. ConjuntoOrdenado: Sem duplicatas, mas ordenado pela posição. ConjuntoOrdenado {5, 4, 3}

  3. Bolsa: Permite duplicatas, não ordenado. Bolsa {1, 2, 2, 5}

  4. 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 onde expr é verdadeiro.

    • reject(expr): Retorna elementos onde expr é falso.

    • collect(expr): Extrai uma propriedade específica de todos os elementos para uma nova coleção.

    • forAll(expr): Retorna verdadeiro se expr é verdadeiro para todos elementos.

    • exists(expr): Retorna verdadeiro se expr é 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)

4. Navegando em Modelos UML

OCL é amplamente utilizado como uma linguagem de navegação para percorrer associações.

Navegação Padrão

Navegue usando o nome do papel oposto. O tipo de retorno depende da multiplicidade:

  • Multiplicidade 1 ou 0..1: Retorna um único objeto.

  • Multiplicidade *: Retorna um Conjunto (ou ConjuntoOrdenado se 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. Use resultado para 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

Restrições de Navegação e Relacionamento

-- 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)