O.C.L. Object Constraint Language Françoise Schlienger FIIFO4 2004-2005 F. Schlienger 2000/2001
O.C.L. : Object Constraint Language Langage formel de description des contraintes d'UML, standardisé par l'OMG. C'est un langage de spécification et non un langage de programmation. Il permet : de modéliser de manière plus précise de mieux documenter un modèle de rester indépendant de l'implémentation d'identifier les responsabilités de chaque composant. Biblio : The Object Constraint Language : getting your models ready for MDA (Model Driven Architecture), Jos Warmer, Anneke Kleppe, (Août 2003) F. Schlienger 2000/2001
Différentes sortes de contraintes Les invariants de classe ou de type : contraintes (expressions booléennes, sans effet de bord) qui doivent être respectées par toutes les instances de la classe ou du type concerné. Les pré- et post-conditions : contraintes qui doivent être respectées respectivement avant ou après l'exécution d'une opération. F. Schlienger 2000/2001
Les stéréotypes de contraintes « invariant » auquel est associé le mot clé inv « pré-condition » auquel est associé le mot clé pre « post-condition » auquel est associé le mot clé post F. Schlienger 2000/2001
Contexte Chaque contrainte OCL est liée à un contexte définissant l'élément de modélisation auquel la contrainte se rapporte. Une expression OCL est de la forme : context nom de l'élément de modélisation concerné inv | pre | post. Exemple : context Pile inv : NbEléments>=0 Cette contrainte s’applique à tous les éléments de la classe. Représentation graphique : Pile <<invariant>> {NbEléments>=0} Elément /-NbEléments : integer +Ajouter(In Elem:Elément) +Retirer() SesEléments 0..1 * F. Schlienger 2000/2001
Le mot clé : self Il sert à identifier l’instance contextuelle à laquelle la contrainte se rapporte : context Pile inv : self.NbEléments>=0 A la place du mot self, il est possible d’écrire un nom d’instance. context p : Pile inv : p.NbEléments>=0 Les 2 expressions sont équivalentes à : NbEléments>=0 F. Schlienger 2000/2001
Attribut d’instance - Attribut de classe Pour les 2 on utilise la notation pointée. Attribut d’instance : context Pile inv : self.NbEléments>=0 Attribut de classe : context Pile inv: self.NbEléments<Pile.NbMax <<invariant>> {NbElements>=0} {NbElements<Pile.NbMax} Pile /-NbEléments : integer NbMax : integer +Ajouter(In Elem:Elément) +Retirer() F. Schlienger 2000/2001
Les collections OCL permet de gérer 3 types de collections. Set : ensemble non ordonné, sans répétition résultat d’une navigation {C1, C2, C3} Bag: multi-ensemble non ordonné, répétitions possibles navigations combinées {P1,P2,P3,P1,P6,P2} Séquence : ensemble ordonné, répétitions possibles navigation à travers un rôle ordonné {ordered} Pas de collection de collections : mise à plat automatique.
Opérations prédéfinies sur les collections (1) cisEmpty() : booléen retourne vrai si la collection c est vide. cnotEmpty() : booléen retourne vrai si la collection c n’est pas vide. csize() : entier retourne le nombre d’éléments de la collection c. ccount(elem) : entier retourne le nombre de fois où elem apparaît dans la collection c. csum() : T retourne la somme des éléments appartenant à la collection c. cselect(cond) : retourne un sous-ensemble de la collection c dont chaque élément respecte la condition (cond) passée en paramètre. creject(cond) : retourne un sous-ensemble de la collection c dont chaque élément ne respecte pas la condition (cond) passée en paramètre.
Opérations sur les collections (exemples) SERVICE PERSONNES Nom Prénom NbEnfants 1 * SesEmployés unService.SesEmployés représente la collection des employés de unService unService.SesEmployés isEmpty () retourne vrai si unService n’a pas d’employé. unService.SesEmployés size() retourne le nombre d’employés de unService.
Opérations prédéfinies sur les collections (2) ccollect(cond) : retourne une collection dérivée de la collection c dont chaque élément respecte la condition passée en paramètre. Le résultat est un « bag » (multi-ensemble non ordonné, répétitions possibles). On veut savoir quel est le nombre total d’enfants des employés d’un service : unService.SesEmployés collect (P : Personne | P.nbEnfants) ou unService.SesEmployés collect (P | P.nbEnfants) ou unService.SesEmployés collect (nbEnfants) Notation abrégée : unService.SesEmployés.nbEnfants notation utilisée par la suite On peut alors calculer le nombre total d’enfants des employés d’un service unService.SesEmployés.nbEnfants sum ()
Opérations sur les collections (exemples) SERVICE PERSONNES Nom Prénom NbEnfants 1 * SesEmployés unService.SesEmployés.Prénom count(« Jean ») retourne le nombre de personnes prénommées « Jean » unService.SesEmployés.NbEnfants sum() retourne le nombre total des enfants de tous les employés de unService
Opérations sur les collections (exemples) Sous ensemble des Employés de unService dont l’age est > 50 : (3 solutions équivalentes) unService.SesEmployés select (p : Personne | p.age >50) ou unService.SesEmployés select (p | p.age >50) ou unService.SesEmployés select (age >50) On peut combiner les opérations : Dans l’invariant de la classe SERVICE context SERVICE inv : self .SesEmployésselect (âge < 25) isEmpty() signifie que dans chaque service, il n’y a pas d’employé dont l’âge est inférieur à 25 ans.
Opérations prédéfinies sur les collections (3) cincluding(elem) : retourne la collection c en incluant elem. cexcluding(elem) : retourne la collection c en excluant elem. cincludes(elem) : booléen retourne vrai si la collection c inclut elem. cexcludes(elem) : booléen retourne vrai si la collection c n’inclut pas elem. cincludesAll(coll) : booléen retourne vrai si la collection c inclut tous les éléments de coll. cexcludesAll(coll) : booléen retourne vrai si la collection c n’inclut aucun des éléments de coll.
Opérations propres aux séquences sfirst() retourne le premier élément de la séquence s slast() retourne le dernier élément de la séquence s sat(i: integer) retourne l’élément en position i de la séquence s sappend(elem) retourne une séquence contenant tous les éléments de la séquence s, suivi de elem. sprepend(elem) retourne une séquence contenant elem, suivi de tous les éléments de la séquence s.
Quantificateurs : universel et existentiel cforAll(cond) : retourne vrai si tous les éléments de la collection c respectent la condition passée en paramètre. Exemple : dans le cas d’une association parent / enfant Chaque parent d’une personne a bien cette personne comme enfant. context Personne inv : self.SesParents forAll (p : Personne | p.SesEnfant includes (self)) PERSONNE -Nom -Prénom 0..2 SesParents SesEnfants * ou context Personne inv : self.SesParents forAll (p | p.SesEnfant includes (self)) ou context Personne inv : self.SesParents forAll (SesEnfant includes (self))
Quantificateurs : universel et existentiel Il est possible de parcourir plusieurs variables à la fois . Exemple : Les enfants d’une personne ont tous des prénoms différents. context Personne inv : self.SesEnfantsforAll ( e1, e2 : Personne | e1<>e2 implies e1.Prénom <>e2.Prénom) cexists(cond) : retourne vrai si au moins un élément de la collection c respecte la condition passée en paramètre. Dans un groupe d’étudiants, il y a au moins un délégué. context Groupe inv : self.SesEtudiants exists (e : Personne | e.fonction = ‘ Délégué ’)
Unicité cisUnique(expr) : retourne vrai si pour chaque valeur de la collection c l’expression expr retourne une valeur différente. Exemple : Les enfants d’une personne ont tous des prénoms différents. context Personne inv : self.SesEnfantsisUnique(Prénom) Autre manière : context Personne inv : self.SesEnfantsforAll ( e1, e2 : Personne | e1<>e2 implies e1.Prénom <>e2.Prénom)
Dépendance de contraintes Certaines contraintes sont dépendantes d’autres contraintes. 2 formes existent pour gérer cela. if expr1 then expr2 else expr3 endif; Si l’expression expr1 est vraie alors expr2 doit être vraie sinon expr3 doit être vraie. expr1 implies expr2 Cette expression est validée lorsque l’expression expr1 est évaluée à faux ou lorsque expr1 est évaluée à vrai et expr2 est également évaluée à vrai.
Dépendance de contraintes : exemple 1 PERSONNE SesTitulaires SesComptes * -Nom : string -/Age : integer Ddn : date COMPTE -Numéro : integer BANQUE SesClients SesBanques SaBanque SesCompte 1 Pour traduire la règle : une personne de moins de 18 ans n’a pas de compte en banque alors qu’une personne de plus de 18 ans possède au moins un compte. context Personne inv : if Age < 18 then SesComptes isEmpty() else SesComptes notEmpty() endif
Dépendance de contraintes : exemple 2 PERSONNE COMPTE SesTitulaires SesComptes * -Nom : string -/Age : integer Ddn : date -Numéro : integer BANQUE SesClients SesBanques SaBanque SesCompte 1 Si une personne possède au moins un compte bancaire, alors elle est cliente d’au moins une banque. context Personne inv : SesCompte notEmpty() implies SesBanques notEmpty()
Dépendance de contraintes : exemple 3 PERSONNE COMPTE SesTitulaires SesComptes * -Nom : string -/Age : integer Ddn : date -Numéro : integer BANQUE SesClients SesBanques SaBanque SesCompte 1 Si un compte appartient à une banque, son titulaire est client de la banque. context Compte inv : self.SaBanque.SesClients includes SonTitulaire
Exemple 4 PERSONNE SesEnfants * -Nom -Prénom 0..2 SesParents Si une personne a des enfants, alors tous ses enfants ont bien cette personne comme parent. context Personne inv : self.SesEnfants notEmpty() implies SesEnfants forAll (p : Personne | p.SesParents includes (self))
Opération définie sur les classes Classe.allInstances : retourne l ’ensemble des instances de la classe. Exemple : pour spécifier que le nombre de personnes modélisées dans une classe est inférieur à 500. context Personne inv : Personne. allInstances size < 500 Pour définir un identifiant : Personne. allInstances isUnique(numINSEE)
Types énumérés FEUILLE -CouleurFond : Couleur -CouleurStylo : Couleur Ils sont autorisés dans les expressions OCL. La valeur d ’un type énuméré est indiquée en faisant précéder son nom du symbole # Soit le type énuméré Couleur = {bleu, rouge, noir, blanc} La couleur du stylo est noire sauf lorsque la couleur de fond est noire ; dans ce cas la couleur du stylo sera blanche. FEUILLE -CouleurFond : Couleur -CouleurStylo : Couleur context Feuille inv : if CouleurFond <> #noir then CouleurStylo = #noir else CouleurStylo = #blanc endif
Éléments particuliers des pré- et post-conditions Les pré- et post-conditions : contraintes qui doivent être respectées respectivement avant ou après l'exécution d'une opération. result référence la valeur retournée par une opération. Pour désigner la valeur d’une propriété avant l’exécution d’une opération, le terme @pre est post-fixé au nom de la propriété. O.oclIsNew () retourne vrai si l ’objet O a été créé dans l’opération. Ces opérateurs ne peuvent être utilisés que dans des post-conditions.
Éléments particuliers des pré- et post-conditions Exemple Dans le cas d’un compte bancaire, sur l’opération débiter() on veut que : avant l ’exécution : la somme à débiter soit strictement positive et après l’exécution : l’attribut solde doit avoir pour valeur la différence de sa valeur avant l’appel et de la somme passée en paramètre. context Compte :: débiter (somme : integer) pre : somme >O post : solde = solde@pre - somme Attention : on ne décrit pas comment l’opération est réalisée mais des contraintes sur l’état avant et après son exécution. Dans un autre cas on pourrait avoir post : solde <= solde@pre - somme COMPTE -Numéro : integer +débiter (somme : integer)
Exemple 5 PERSONNE SesEnfants * -Nom -Prénom -Sexe +NbFilles() 0..2 SesParents SesEnfants * Spécifier NbFilles qui renvoie un entier correspondant au nombre de filles d’une personne. context Personne :: NbFilles () : entier post : result = self.SesEnfants select (fille : Personne |fille.Sexe = #féminin) size() ou context Personne :: NbFilles () : entier post : result = self.SesEnfants select (fille |fille.Sexe = #féminin) size() ou context Personne :: NbFilles () : entier post : result = self.SesEnfants select (Sexe = #féminin) size()
Associations qualifiées AVION Num : int * SIEGE Rangée : entier Lettre : caractère Cl : Classe Classe : {économique, business} AVION Num : int Rangée : entier Lettre : caractère 0..1 SIEGE Cl : Classe SonSiège unAvion.SonSiège [uneRangée, uneLettre] désigne un siège donné de manière non ambiguë Si dans un avion il y a un siège sur la rangée 13, il est de type ’économique’ context Avion inv : forAll (lettre : caractère | self.SonSiège [13, lettre] exists() implies self.SonSiège [13, lettre] . Classe = #économique
Types et sous types de classificateurs O.oclIsTypeOf (t : oclType) : booléen retourne vrai si le type t est le type direct (pas un super-type) de l ’objet O. unPoidsLourd.oclIsTypeOf(PoidsLourd) retourne vrai unPoidsLourd.oclIsTypeOf(Véhicule) retourne faux O.oclIsKindOf (t : oclType) : booléen retourne vrai si le type t est le type direct ou un super-type de l ’objet O. unPoidsLourd.oclIsKindOf(PoidsLourd) retourne vrai unPoidsLourd.oclIsTypeOf(Véhicule) retourne vrai O.oclAsType (t : oclType) : objet force l ’objet O à être du type t. Le type t est un sous-type du type de l ’objet O. unPoidsLourd.oclAsType (Camion) est évalué comme un objet de type Camion … aux risques et périls de l ’utilisateur.
Les variables Lorsqu ’une expression apparaît plusieurs fois dans une condition, il n ’est pas nécessaire de la réécrire plusieurs fois et une variable peut être définie. let variable : type = expression in PERSONNE -Nom -Prénom -Seuil1 -Seuil2 -/Etat ACTIVITE -Libellé -Salaire SesEmplois context Personne inv : let Revenus : integer = self.SesEmplois.Salaire sum() in if Revenus < Personne.Seuil1 then Etat = #niveau1 else if Revenus < Personne.Seuil2 then Etat = #niveau2 else Etat = #niveau3 endif
Exemple 6 PERSONNE COMPTE SesTitulaires SesComptes 1..4 * -Nom : string -Numéro : integer CARTE-BLEUE SonSignataire SesCartes 1 SonCompte P1:PERSONNE C1:COMPTE P2:PERSONNE C2:COMPTE CB1:CARTE-BLEUE Nom=Marie Nom=Paul Numéro=123456 Numéro=345678 SeTitulaires SesComptes SesTitulaires Numéro=13579 SonSignataire SesCartes SonCompte
Pour être plus précis … context CARTE-BLEUE inv : self.SonCompte.Titulaires includes(self.signataire) ou SonCompte.Titulaires includes(Signataire)