Un guide complet sur le langage de contrainte des objets (OCL)
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.

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
-
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).
-
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.
-
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.
-
É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 + best 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 :
vrai,faux(Opérations :et,ou,non,implique,ou exclusif) -
Entier / Réel : Nombres (Opérations :
+,-,*,/,abs,max,min,div,mod) -
Chaîne : Texte (Opérations :
taille,concat,sousChaîne,enEntier)
Collections
Les collections sont des types abstraits utilisés pour contenir plusieurs éléments. Elles sont accessibles à l’aide de la notation -> notation.
-
Ensemble : Ensemble mathématique (pas de doublons, non ordonné).
Ensemble {1, 2, 5} -
EnsembleOrdonné : Pas de doublons, mais ordonné par position.
EnsembleOrdonné {5, 4, 3} -
Sac : Permet les doublons, non ordonné.
Sac {1, 2, 2, 5} -
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ùexprest vrai. -
reject(expr): Retourne les éléments oùexprest faux. -
collect(expr): Extrait une propriété spécifique de tous les éléments dans une nouvelle collection. -
forAll(expr): Retournevraisiexprest vrai pour tous éléments. -
exists(expr): Retournevraisiexprest 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)
OCL est largement utilisé comme langage de navigation pour parcourir les associations.
Naviguez en utilisant le nom du rôle opposé. Le type de retour dépend de la multiplicité :
-
Multiplicité
1ou0..1: Renvoie un objet unique. -
Multiplicité
*: Renvoie unEnsemble(ouEnsemble 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. Utilisezrésultatpour 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
-- 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)














