Object Constraint Language -OCL-

Object Constraint Language -OCL- ⤳ origine et positionnement

OCL et… UML

OCL ⤳ syntaxe

context Site inv:
    self.enterprise -- retourne l'objet 'e' de type 'Enterprise' ou le singleton '{e}'
    self.enterprise.profile -- retourne un ensemble d'objets 'Profile'
context Enterprise inv:
    site.contact -- 'self' peut être omis, retourne un ensemble d'objets 'Contact'
context Organizational profile inv:
    profile -- retourne l'objet 'p' de type 'Profile' ou le singleton '{p}'
    contact -- interdit !
context Contact inv:
    affiliation -- retourne l'objet 's' de type 'Site' ou le singleton '{s}'
    affiliation.enterprise.status -- retourne un 'bag' (un seul membre) d'objets 'Legal status'
Legal status::Trust -- accès à une valeur de type énuméré

OCL ⤳ règles métier (business rules)

Les profils d'un site en tant qu'organisation sont inclus dans l'ensemble des profils de la compagnie de ce site :

context Site inv:
    enterprise.profile->includesAll(profile)

Si l'on réunit tous les profils du site et de la compagnie d'un contact donné, on sait que les rôles que joue ce contact appartiennent nécessairement à la réunion* :

context Contact inv:
    (affiliation.enterprise.profile->union(affiliation.profile))->
    includesAll(organizational profile.profile)

Unicité :

context Person inv:
    Person.allInstances()->isUnique(PIN) -- 'PIN' est un attribut de 'Person'

*Sur-spécification

OCL ⤳ types

OCL ⤳ opérateurs prédéfinis

Person.allInstances() returns Set(Person)

context Person inv:
    let FranckBarbier : Person = Person.allInstances()->select(PIN = ‘1630125388055’) in
        FranckBarbier.oclIsTypeOf(Man) = true
        FranckBarbier.oclIsTypeOf(Person) = false 
        FranckBarbier.oclIsKindOf(Man) = true
        FranckBarbier.oclIsKindOf(Person) = true
        FranckBarbier.oclAsType(Man).beard = true
context Woman::bear() : Person
    pre: oclInState(Pregnant::Delivery)
    post: result.oclIsNew()
    post: result.oclIsTypeOf(Man) implies sons->size() = sons@pre->size() + 1
    post: result.oclIsTypeOf(Woman) implies daughters->size() = daughters@pre->size() + 1

OCL et… UML ⤳ types

OCL ⤳ exercice

Exprimer en OCL* : «  frères » ainsi que le fait que l'être humain dont le n°SS est 1630125388055 a une sœur, pas de frère et deux nièces !

*Les éléments (logiques) du langage de base sont not, true, false, and, or et implies

OCL ⤳ solution

context Être humain::frères : Collection(Être humain)
    oclAsType(Homme).frère->union(oclAsType(Femme).frère)
context Être humain::frères : Collection(Être humain)
    if not oclAsType(Homme).oclIsUndefined()
    then oclAsType(Homme).frère
    else oclAsType(Femme).frère
context Être humain::frères : Collection(Être humain)
    self.homme.frère->union(self.femme.frère)

OCLoclIsUndefined() et oclIsInvalid()

UML Composition ⤳ subtlety*

*OCL

context Moteur inv:
    not self.voiture.oclIsUndefined() implies self.camion.oclIsUndefined()

OCL ⤳ opérations

Les opérations OCL sont par définition sans effet de bord (query) ; OCL est en effet un langage déclaratif !

Contenu d'une opération :

context Enterprise::employees() : Set(Contact)
    body: site.contact

Pré and post-conditions :

context Distributeur automatique bancaire::distribuer(montant : Integer)
    pre: montant = 20 or montant = 40 -- Etc.
    pre: en stock >= montant
    post: en stock = en stock@pre - montant

OCL ⤳ clauses def & let

Clause def (accessibilité depuis l'extérieur, i.e., export) :

context Être humain def:
    sexe : String = self.n°SS.substring(1,1)

Clause let (contextuelle à expression) :

context Être humain inv:
    let sexe : String = self.n°SS.substring(1,1) in
    if sexe = '1' then
        -- etc.
    else 
        -- etc.
    endif

OCL ⤳ clauses attr, init & derive

Clause attr :

context Être humain def:
    attr proches : Tuple(frères: Set(Homme),sœurs: Set(Femme)) =
    Tuple {oclAsType(Homme).frère->union(oclAsType(Femme).frère),
           oclAsType(Homme).sœur->union(oclAsType(Femme).sœur)}

Clauses init (valeur initiale ou par défaut) et derive :

context Enterprise::status
    init: Legal status::Sole proprietorship
    derive: if stock quotation = true then
        Legal status::Trust
    endif

OCL ⤳ opérations essentielles sur les collections

Soit c de type Collection(Être humain) :

c->iterate(x | …) -- est la forme générique (par commodité, il y a des formes simplifiées)
c->isUnique(n°SS) ≍ id. (retourne 'Boolean')
*c->select(x | x.n°SS.substring(1,1) = ‘1’) -- retourne 'Collection(Être humain)'
*c->reject(x | x.n°SS.substring(1,1) = ‘2’) -- retourne 'Collection(Être humain)'
c->forAll(x | …) -- ≍ ∀ (retourne 'Boolean')
c->exists(x | …) -- ≍ ∃ (retourne 'Boolean')
c->one() -- ≍ ∃! (retourne 'Boolean')
c->any(not (n°SS.oclIsUndefined() and n°SS.substring(2,3) = ‘63’))
    -- retourne un et un seul élément au hasard de la collection

*Oubli du test oclIsUndefined()

OCL ⤳ opérations courantes sur les collections

Soit c de type Collection(X) :

c->size() -- retourne 'Integer'
c->includes(x) -- retourne 'Boolean'
c->excludes(x) -- retourne 'Boolean'
c->count(x) -- retourne 'Integer'
c->includesAll(c’) -- retourne 'Boolean' avec 'c’' de type 'Collection(X)'
c->excludesAll(c’) -- retourne 'Boolean' avec 'c’' de type 'Collection(X)'
c->isEmpty() -- retourne 'Boolean'
c->notEmpty() -- retourne 'Boolean'
c->sum() -- retourne 'X' avec 'X' supportant l’opération +
c->product(c’) : Set(Tuple(first : X,second : Y)) avec 'c’' de type 'Collection(Y)'

OCL ⤳ collections ordonnées

context Route touristique inv:*
    let x : OrderedSet(Segment de route touristique) = self.segment de route touristique in
    x->iterate(i : Segment de route touristique | i <> x->last() and
        let j : Segment de route touristique = x->subOrderedSet(indexOf(i) +
            1,x->last())->first() in i.arrivée = j.départ)

*Tous les segments de route touristique d’une route touristique donnée, sont tels que chacun doit avoir comme ville de départ, la ville d’arrivée de son prédécesseur dans l’ordre posé par la contrainte {ordered}

OCL ⤳ type OclMessage

context Composition.composé::emergent()
post: composant.provided.ownedOperation->forAll(o |
    let messages : Sequence(OclMessage) =
    self.part^^o() in messages->isEmpty()) -- part axiomatique

context Composition.composé::resultant()
post: composant.provided.ownedOperation->exists(o |
    let messages : Sequence(OclMessage) =
    self.part^^o() in messages->notEmpty()) -- part axiomatique

OCL et méta-modélisation (UML 2.1.1, Superstructure, p. 523)

context State inv:
    isComposite = true implies region->size() >= 1
    isOrthogonal = true implies region->size() >= 2
    isSimple = true implies region->isEmpty() and submachine->isEmpty()
    isSubmachineState = true implies submachine->notEmpty()

OCL avancé (et limites)

OCL :

context Collaborateur::agrégation(société : String) :
    Bag(Tuple(ville : String, somme_des_âges : Integer))
    body: Collaborateur.allInstances()->select(c | c.société =
        société)->collect(ville)->asSet()->collect(v |
            Tuple {ville : String = v, somme_des_âges : Integer =
                Collaborateur.allInstances()->select(c | c.société = société
                    and c.ville = v).âge->sum()})

SQL :

SELECT Collaborateur.ville, sum(Collaborateur.age) FROM Collaborateur
    WHERE Collaborateur.societe = ‘ALE’ GROUP BY Collaborateur.ville;

© Franck Barbier