de_DEen_USes_ESfa_IRfr_FRhi_INid_IDjapl_PLpt_PTru_RUvizh_CNzh_TW

Sur la base des documents de cours fournis, ce guide couvre les concepts fondamentaux, la syntaxe et les applications pratiques du langage de contrainte des objets (OCL) dans le cadre de la modélisation UML.

Objct Constraint Language (OCL)


1. Introduction au OCL

Pourquoi avons-nous besoin du OCL ?

Bien que les diagrammes UML (comme les diagrammes de classes) soient excellents pour visualiser la structure, ils ne peuvent pas exprimer tous les aspects pertinents d’une spécification.

  • Langage naturel est ambigu.

  • Les langages formels traditionnels sont précis mais trop difficiles à lire et à écrire pour les modélisateurs de systèmes moyens.

  • OCL ponte ce fossé : c’est un langage formel utilisé pour exprimer des contraintes précises, tout en restant facile à lire et à écrire.

Caractéristiques fondamentales du OCL

  1. Langage d’expression pur : Les expressions n’ont pas d’effets secondaires. L’évaluation d’une expression OCL renvoie une valeur, mais ne peut pas modifier l’état du système. Elle peut toutefois décrire un changement d’état (par exemple, dans une post-condition).

  2. Ce n’est pas un langage de programmation : Vous ne pouvez pas écrire de logique de programme, de flux de contrôle, ni invoquer des processus/opérations qui modifient l’état.

  3. Langage typé : Chaque expression a un type. Les expressions bien formées doivent obéir aux règles de conformité des types. Chaque classificateur UML est un type OCL.

  4. Évaluation instantanée : L’état des objets ne peut pas changer pendant l’évaluation d’une expression OCL.


2. Syntaxe et sémantique de base

Contexte et Self

Chaque expression OCL est évaluée dans le contexte d’un classificateur UML spécifique (classe, interface, etc.).

  • contexte: Définit la classe à laquelle la contrainte s’applique.

  • self: Un mot réservé faisant référence à l’instance spécifique de la classe du contexte en cours d’évaluation.

contexte Person
-- 'self' fait référence à une instance spécifique de Person

Commentaires et opérateurs

  • Commentaires : Noté par -- (par exemple, -- ceci est un commentaire).

  • Opérateurs infixe : Vous pouvez utiliser des opérateurs mathématiques/logiques standards (+-=<). Conceptuellement, a + b est identique à a.+(b).

Accès aux propriétés

Utilisez la notation pointée pour accéder aux attributs ou aux opérations. Les parenthèses sont obligatoires pour les opérations, même si elles n’ont pas de paramètres.

contexte Personne
self.age             -- Accède à l'attribut 'age'
self.stockPrice()    -- Appelle l'opération 'stockPrice'

3. Types et collections

Types prédéfinis de base

OCL inclut des types standards avec des opérations spécifiques :

  • Booléen : vraifaux (Opérations : etounonimpliqueou exclusif)

  • Entier / Réel : Nombres (Opérations : +-*/absmaxmindivmod)

  • Chaîne : Texte (Opérations : tailleconcatsousChaîneenEntier)

Collections

Les collections sont des types abstraits utilisés pour contenir plusieurs éléments. Elles sont accessibles à l’aide de la notation -> notation.

  1. Ensemble : Ensemble mathématique (pas de doublons, non ordonné). Ensemble {1, 2, 5}

  2. EnsembleOrdonné : Pas de doublons, mais ordonné par position. EnsembleOrdonné {5, 4, 3}

  3. Sac : Permet les doublons, non ordonné. Sac {1, 2, 2, 5}

  4. Séquence : Permet les doublons, ordonné. Séquence {1, 2, 5, 10}

    • Remarque : Le .. La notation crée une séquence d’entiers consécutifs :Séquence {1..5}.

Opérations clés sur les collections

  • Communes : taille()estVide()nEstPasVide()contient(v)exclut(v)nombre(v)somme().

  • Itérateurs (Cruciaux pour OCL) :

    • sélectionner(expr): Retourne les éléments où expr est vrai.

    • reject(expr): Retourne les éléments où expr est faux.

    • collect(expr): Extrait une propriété spécifique de tous les éléments dans une nouvelle collection.

    • forAll(expr): Retourne vrai si expr est vrai pour tous éléments.

    • exists(expr): Retourne vrai si expr est vrai pour au moins un élément.

    • iterate(elem; acc = init | expr): La base générique pour tous les itérateurs.

Variations de syntaxe des itérateurs :

self.employee->select(age > 50)
self.employee->select(p | p.age > 50)
self.employee->select(p: Person | p.age > 50)

4. Navigation dans les modèles UML

OCL est largement utilisé comme langage de navigation pour parcourir les associations.

Navigation standard

Naviguez en utilisant le nom du rôle opposé. Le type de retour dépend de la multiplicité :

  • Multiplicité 1 ou 0..1: Renvoie un objet unique.

  • Multiplicité *: Renvoie un Ensemble (ou Ensemble ordonné si marqué {ordonné}).

contexte Company
self.manager.age > 40               -- manager est 0..1, renvoie un objet Personne
self.employee->notEmpty()           -- employee est 0..*, renvoie un Ensemble

Si un nom de rôle est manquant, utilisez le nom en minuscules de la classe à l’autre extrémité (par exemple, self.bank).

Classes d’association

Naviguez vers une classe d’association en utilisant le nom en minuscules de la classe d’association.

contexte Person
self.job->size() >= 1  -- 'job' est la classe d'association entre Personne et Société

Associations récursives

Lorsqu’une association revient sur la même classe (par exemple, mariage entre deux Personnes), vous devez préciser la direction en utilisant des crochets [nomRôle].

contexte Personne
self.marriage[épouse]->select(m | m.terminé = false)->size() = 1

Associations qualifiées

Utilisez les attributs de qualificateur entre crochets pour sélectionner un objet spécifique.

contexte Banque
self.client[12345]  -- Retourne la personne spécifique ayant le numéro de compte 12345
self.client         -- Retourne un ensemble de tous les clients

Re-typage (conversion)

Utilisez oclAsType() pour accéder aux propriétés d’un sous-type ou à une propriété de la superclasse masquée.

self.oclAsType(Sous).p       -- Accède à la propriété 'p' définie dans le sous-type
self.oclAsType(Sur).p     -- Accède à la propriété 'p' définie dans la superclasse

5. Rédaction des contraintes

1. Invariants (inv)

Conditions qui doivent toujours être vraies pour toutes les instances d’une classe.

contexte Société 
inv: self.nombreEmployés <= 50
inv PME: self.coursAction() > 0  -- 'PME' est un nom facultatif pour la contrainte

2. Conditions préalables et postérieures (prépost)

Contraintes liées aux opérations.

  • pré: Doit être vrai avant l’exécution.

  • post: Doit être vrai après exécution. Utilisez résultat pour la valeur de retour.

  • @pre: Utilisé dans les post-conditions pour faire référence à la valeur d’une propriété avant l’opération a commencé.

contexte Personne::revenu(): Entier
pré adulte : self.age >= 18
post : resultOK : résultat < 5000

contexte Société::embaucherEmployé(p : Personne)
post : employé = employé@pre->inclure(p)

3. Expressions Body, Initial et Dérivée

  • body: Définit le résultat d’une opération de requête (peut être récursive).

  • init: Définit la valeur initiale d’un attribut.

  • dérive: Définit un attribut dérivé.

contexte Personne::revenu(): Entier
body : self.emploi.salaire->somme()

contexte Personne::estMarié : Booléen
init : faux

contexte Société::nbEmployés : Entier
dérive : self.employé->taille()

6. Fonctionnalités avancées OCL

Expressions Let et Def

  • soit: Définit une variable locale dans une seule expression.

    soit totHeures : Entier = self.travailleSur->collect(heures)->somme() dans totHeures >= 30
    
  • def: Définit des variables ou des opérations réutilisables attachées à un classificateur.

    def : aTitre(t : Chaîne) : Booléen = self.emploi->existe(titre = t)
    

Valeurs non définies (logique à 3 valeurs)

Si une sous-expression est non définie (par exemple, naviguer vers une association nulle), toute l’expression est non définie. Toutefois, les opérateurs booléens ont des exceptions de court-circuit :

  • vrai ou <non défini> = vrai

  • faux et <non défini> = faux

  • faux implique <non défini> = vrai

  • <non défini> implique vrai = vrai

Fonctionnalités de classe et propriétés d’objet prédéfinies

  • tousLesInstances(): Retourne un ensemble de toutes les instances existantes d’une classe.

    Personne.tousLesInstances()->taille() <= 100
    
  • Propriétés d’objet : oclEstDuType(Type)oclEstUnSousTypeDe(Type)oclEstDansLÉtat(État)oclEstNouveau (utilisé dans les post-conditions).


7. Étude de cas complète : le modèle de l’entreprise

Pour consolider ces concepts, voici une collection de contraintes d’intégrité appliquées à un modèle de base de données d’entreprise (Employés, Départements, Projets, Dépendants).

Contraintes de base sur les attributs

-- L'âge doit être supérieur ou égal à 18
contexte Employé inv : self.age() >= 18

-- La date d'embauche doit être postérieure à la date de naissance
contexte Employé inv : self.dateEmbauche > self.dateNaissance

-- La date de début du manager doit être postérieure à la date d'embauche
contexte Employé inv : self.gère->nonVide() implique self.gère.dateDebut > self.dateEmbauche

Contraintes de navigation et de relation

-- Le superviseur doit être plus âgé et mieux payé que l'employé
contexte Employé
inv : self.superviseur->nonVide() implique self.age() > self.superviseur.age()
inv : self.superviseur->nonVide() implique self.salaire < self.superviseur.salaire

-- Un superviseur doit être embauché avant ses subordonnés
contexte Employé
inv : self.subordonnés->nonVide() implique self.subordonnés->pourTout(e | e.dateEmbauche > self.dateEmbauche)

-- Le manager du département doit être un employé de ce département
contexte Département
inv : self.travaillePour->inclut(self.gère.employé)

-- La localisation du projet doit être l'une des localisations du département
contexte Projet
inv : self.gère.localisations->inclut(self.localisation)

-- Un employé ne peut travailler que sur des projets contrôlés par son département
contexte Employé
inv : self.travaillePour.gère->inclutTout(self.travailleSur.projet)

Contraintes sur les collections et les itérateurs

-- Le numéro de sécurité sociale doit être un identifiant unique pour tous les employés
contexte Employé
inv : Employé.tousLesInstances->pourTout(e1, e2 | e1 <> e2 implique e1.SSN <> e2.SSN)

-- Le nom et la relation du dépendant doivent être uniques par employé
contexte Employé
inv : self.dépendants->pourTout(e1, e2 |
      e1 <> e2 implique (e1.nom <> e2.nom ou e1.relation <> e2.relation))

-- Attribut dérivé : nombre d'employés dans un département
contexte Département
inv : self.nbrEmployés = self.travaillePour->taille()

-- Un employé travaille sur au plus 4 projets
contexte Employé
inv : self.travailleSur->taille() <= 4

Logique complexe utilisant laisser et sélectionner

-- Les heures de travail totales doivent être comprises entre 30 et 50 par semaine
contexte Employé 
inv : laisser totHeures : Entier = self.travailleSur->collecter(heures)->somme() 
     dans totHeures >= 30 et totHeures <= 50

-- Au plus 2 employés peuvent travailler moins de 10 heures sur un projet spécifique
contexte Projet 
inv : self.travailleSur->sélectionner(heures < 10)->taille() <= 2

-- Seuls les gestionnaires de département peuvent travailler moins de 5 heures sur un projet
contexte Employé 
inv : self.travailleSur->sélectionner(heures < 5)->nonVide() implique 
     self.travaillePour.gère.employé = self

-- Les employés sans subordonnés doivent travailler au moins 10 heures sur TOUS leurs projets
contexte Employé 
inv : self.subordonnés->estVide() implique self.travailleSur->pourTout(heures >= 10)

-- Le gestionnaire de département doit travailler au moins 5 heures sur tous les projets contrôlés par le département
contexte Département 
inv : self.contrôle->pourTout(p : Projet | 
      self.gère.employé.travailleSur->sélectionner(heures >= 5)->contient(p))

Empêcher les cycles (logique récursive)

-- Un employé ne peut pas se superviser lui-même
contexte Employé inv : self.subordonnés->exclut(self)

-- La hiérarchie de supervision ne doit pas être cyclique
contexte Employé 
déf : tousLesSubordonnés = self.subordonnés->union(
          self.subordonnés->collecter(e : Employé | e.tousLesSubordonnés))
inv : self.tousLesSubordonnés->exclut(self)