0% ont trouvé ce document utile (0 vote)
82 vues92 pages

Introduction à UML pour Débutants

Curs UML paris 13

Transféré par

El Varough Haiballa
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
82 vues92 pages

Introduction à UML pour Débutants

Curs UML paris 13

Transféré par

El Varough Haiballa
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Introduction à UML

Ingénieurs Sup Galilée – Ingénieur Informatique 1° année – Paris 13


2017-2018

Enseignant
John Chaussard
Université Paris 13, LAGA, Bureau D402
chaussard@[Link]
2
Ce cours est extrait du cours de Laurent Audibert (LIPN, Paris XIII) disponible
sur le site [Link], avec des retouches mineures :

[Link]

Vous pouvez retrouver ce cours ainsi que de nombreuses informations


complémentaires dans le livre de Laurent Audibert, « UML2 – de
l’apprentissage à la pratique », ISBN 2340002044

Le livre est disponible à la bibliothèque universitaire du site de Villetaneuse


([Link]

3
4
1 Diagramme de cas d'utilisation (Use Case
Diagram)
1 Diagramme de cas d'utilisation (Use Case Diagram) ............................................ 5
1.1 Introduction .............................................................................................. 5
1.2 Éléments des diagrammes de cas d'utilisation ............................................... 6
1.2.1 Acteur ................................................................................................ 6
1.2.2 Cas d'utilisation ................................................................................... 6
1.2.3 Représentation d'un diagramme de cas d'utilisation ................................. 7
1.3 Relations dans les diagrammes de cas d'utilisation ......................................... 7
1.3.1 Relations entre acteurs et cas d'utilisation .............................................. 7
Relation d'association ................................................................................... 7
Multiplicité .................................................................................................. 8
Acteurs principaux et secondaires .................................................................. 8
Cas d'utilisation interne ................................................................................ 8
1.3.2 Relations entre cas d'utilisation ............................................................. 8
1-3-2-a. Types et représentations .................................................................. 9
1-3-2-b. Relation d'inclusion .......................................................................... 9
1-3-2-c. Relation d'extension ....................................................................... 10
1-3-2-d. Relation de généralisation .............................................................. 10
1.3.3 1-3-3. Relations entre acteurs ............................................................. 10
1.4 1-4. Notions générales du langage UML ...................................................... 11
1.4.1 1-4-1. Note ....................................................................................... 11
1.5 1-5. Modélisation des besoins avec UML...................................................... 12
1.5.1 1-5-1. Comment identifier les acteurs ? ................................................ 12
1.5.2 1-5-2. Comment recenser les cas d'utilisation ? ..................................... 12
1.5.3 1-5-4. Remarques.............................................................................. 13
1-5-4-a. Concernant les relations dans les cas d'utilisation .............................. 13
1-5-4-b. Concernant les cas d'utilisation ....................................................... 13

1.1 Introduction
Bien souvent, la maîtrise d'ouvrage et les utilisateurs ne sont pas des informaticiens. Il
leur faut donc un moyen simple d'exprimer leurs besoins. C'est précisément le rôle des
diagrammes de cas d'utilisation qui permettent de recueillir, d'analyser et d'organiser les
besoins, et de recenser les grandes fonctionnalités d'un système. Il s'agit donc de la
première étape UML d'analyse d'un système.

Un diagramme de cas d'utilisation capture le comportement d'un système, d'un sous-


système, d'une classe ou d'un composant tel qu'un utilisateur extérieur le voit. Il scinde
la fonctionnalité du système en unités cohérentes, les cas d'utilisation, ayant un sens

5
pour les acteurs. Les cas d'utilisation permettent d'exprimer le besoin des utilisateurs
d'un système, ils sont donc une vision orientée utilisateur de ce besoin au contraire d'une
vision informatique.

Il ne faut pas négliger cette première étape pour produire un logiciel conforme aux
attentes des utilisateurs. Pour élaborer les cas d'utilisation, il faut se fonder sur des
entretiens avec les utilisateurs.

1.2 Éléments des diagrammes de cas d'utilisation

1.2.1 Acteur

Un acteur est l'idéalisation d'un rôle joué par une personne externe, un processus ou une
chose qui interagit avec un système.

Il se représente par un petit bonhomme (Figure 1.1) avec son nom (i.e. son rôle) inscrit
dessous.

Figure 1.1 : Exemple de représentation d'un acteur.

Il est également possible de représenter un acteur sous la forme d'un classeur stéréotypé
(voir 1.4.4 Stéréotype) <<actor>> (Figure 1.2).

Figure 1.2 : Exemple de représentation d'un acteur sous la forme d'un classeur.

1.2.2 Cas d'utilisation

Un cas d'utilisation est une unité cohérente représentant une fonctionnalité visible de
l'extérieur. Il réalise un service de bout en bout, avec un déclenchement, un déroulement
et une fin, pour l'acteur qui l'initie. Un cas d'utilisation modélise donc un service rendu
par le système, sans imposer le mode de réalisation de ce service.

Un cas d'utilisation se représente par une ellipse (Figure 1.3) contenant le nom du cas
(un verbe à l'infinitif), et optionnellement, au-dessus du nom, un stéréotype (cf. section
1.4.4 Stéréotype).

6
Figure 1.3 : Exemple de représentation d'un cas d'utilisation.

1.2.3 Représentation d'un diagramme de cas d'utilisation

Figure 1.5 : Exemple simplifié de diagramme de cas d'utilisation modélisant une borne
d'accès à une banque.

Comme le montre la Figure 1.5, la frontière du système est représentée par un cadre. Le
nom du système figure à l'intérieur du cadre, en haut. Les acteurs sont à l'extérieur et
les cas d'utilisation à l'intérieur.

1.3 Relations dans les diagrammes de cas d'utilisation

1.3.1 Relations entre acteurs et cas d'utilisation

Relation d'association

Figure 1.6 : Diagramme de cas d'utilisation représentant un logiciel de partage de


fichiers.

Une relation d'association est chemin de communication entre un acteur et un cas


d'utilisation et est représenté un trait continu (cf. figure 1.5 ou 1.6).

7
Multiplicité

Lorsqu'un acteur peut interagir plusieurs fois avec un cas d'utilisation, il est possible
d'ajouter une multiplicité sur l'association du côté du cas d'utilisation. Le symbole *
signifie plusieurs (figure 1.6), exactement n s'écrit tout simplement n, n..m signifie entre
n et m, etc. Préciser une multiplicité sur une relation n'implique pas nécessairement que
les cas sont utilisés en même temps.

La notion de multiplicité n'est pas propre au diagramme de cas d'utilisation. Nous en


reparlerons dans le chapitre consacré au diagramme de classes.

Acteurs principaux et secondaires

Un acteur est qualifié de principal pour un cas d'utilisation lorsque ce cas rend service à
cet acteur. Les autres acteurs sont alors qualifiés de secondaires. Un cas d'utilisation a au
plus un acteur principal. Un acteur principal obtient un résultat observable du système
tandis qu'un acteur secondaire est sollicité pour des informations complémentaires. En
général, l'acteur principal initie le cas d'utilisation par ses sollicitations. Le stéréotype
<< primary >> vient orner l'association reliant un cas d'utilisation à son acteur principal,
le stéréotype << secondary >> est utilisé pour les acteurs secondaires (figure 1.6).

Cas d'utilisation interne

Quand un cas n'est pas directement relié à un acteur, il est qualifié de cas d'utilisation
interne.

1.3.2 Relations entre cas d'utilisation

Figure 1.7 : Exemple de diagramme de cas d'utilisation.

8
Types et représentations

Il existe principalement deux types de relations :

• les dépendances stéréotypées, qui sont explicitées par un stéréotype (les plus
utilisés sont l'inclusion et l'extension) ;
• et la généralisation/spécialisation.

Une dépendance se représente par une flèche avec un trait pointillé (figure 1.7). Si le cas
A inclut ou étend le cas B, la flèche est dirigée de A vers B.

Le symbole utilisé pour la généralisation est une flèche avec un trait plein dont la pointe
est un triangle fermé désignant le cas le plus général (figure 1.7).

Relation d'inclusion

Un cas A inclut un cas B si le comportement décrit par le cas A inclut le comportement du


cas B : le cas A dépend de B. Lorsque A est sollicité, B l'est obligatoirement, comme une
partie de A. Cette dépendance est symbolisée par le stéréotype << include >> (figure
1.7). Par exemple, l'accès aux informations d'un compte bancaire inclut nécessairement
une phase d'authentification avec un identifiant et un mot de passe (figure 1.7).

Les inclusions permettent essentiellement de factoriser une partie de la description d'un


cas d'utilisation qui serait commune à d'autres cas d'utilisation (cf. le cas S'authentifier
de la figure 1.7).

Les inclusions permettent également de décomposer un cas complexe en sous-cas plus


simples (figure 1.8).

Cependant, il ne faut surtout pas abuser de ce type de décomposition : il faut éviter de


réaliser du découpage fonctionnel d'un cas d'utilisation en plusieurs sous-cas d'utilisation
pour ne pas retomber dans le travers de la décomposition fonctionnelle.

Attention également au fait que, les cas d'utilisation ne s'enchaînent pas,


puisqu'il n'y a aucune représentation temporelle dans un diagramme de cas
d'utilisation. Sur la figure 1.7, la borne demande-t-elle d’abord aux clients de
s’authentifier puis leur demande l’opération à réaliser, ou est-ce l’inverse ?

En général, on dira qu’un cas d’utilisation A inclut un cas B si l’exécution réussie


de A nécessite l’exécution de B, et que tant que B n’est pas terminé, A ne peut
pas se terminer.

9
Figure 1.8 : Relations entre cas pour décomposer un cas complexe.

Relation d'extension

La relation d'extension est probablement la plus utile, car elle a une sémantique qui a un
sens du point de vue métier au contraire des deux autres qui sont plus des artifices
d'informaticiens.

On dit qu'un cas d'utilisation A étend un cas d'utilisation B lorsque le cas d'utilisation A
peut être appelé au cours de l'exécution du cas d'utilisation B. Exécuter B peut
éventuellement entraîner l'exécution de A : contrairement à l'inclusion, l'extension est
optionnelle. Cette dépendance est symbolisée par le stéréotype << extend >> (figure
1.7).

L'extension peut intervenir à un point précis du cas étendu. Ce point s'appelle le point
d'extension. Il porte un nom, qui figure dans un compartiment du cas étendu sous la
rubrique point d'extension, et est éventuellement associé à une contrainte indiquant le
moment où l'extension intervient. Une extension est souvent soumise à condition.
Graphiquement, la condition est exprimée sous la forme d'une note. La figure 1.7
présente l'exemple d'une banque où la vérification du solde du compte n'intervient que si
la demande de retrait dépasse 20 euros.

Relation de généralisation

Un cas A est une généralisation d'un cas B si B est un cas particulier de A. Dans la figure
1.7, la consultation d'un compte via Internet est un cas particulier de la consultation.
Cette relation de généralisation/spécialisation est présente dans la plupart des
diagrammes UML et se traduit par le concept d'héritage dans les langages orientés objet.

1.3.3 Relations entre acteurs

La seule relation possible entre deux acteurs est la généralisation : un acteur A est une
généralisation d'un acteur B si l'acteur A peut être substitué par l'acteur B. Dans ce cas,
tous les cas d'utilisation accessibles à A le sont aussi à B, mais l'inverse n'est pas vrai.

Le symbole utilisé pour la généralisation entre acteurs est une flèche avec un trait plein
dont la pointe est un triangle fermé désignant l'acteur le plus général (comme nous
l'avons déjà vu pour la relation de généralisation entre cas d'utilisation).

10
Par exemple, la figure 1.9 montre que le directeur des ventes est un préposé aux
commandes avec un pouvoir supplémentaire : en plus de pouvoir passer et suivre une
commande, il peut gérer le stock. Par contre, le préposé aux commandes ne peut pas
gérer le stock.

Figure 1.9 : Relations entre acteurs.

1.4 Notions générales du langage UML


Les éléments du langage UML que nous abordons ici ne sont pas spécifiques au
diagramme de cas d'utilisation, mais sont généraux. Nous avons déjà utilisé certains de
ces éléments dans ce chapitre et nous utiliserons les autres dans les chapitres qui
suivent, notamment dans le chapitre sur les diagrammes de classes.

1.4.1 Note

Figure 1.11 : Exemple d'utilisation d'une note pour préciser que le solde d'un compte doit
toujours être positif.

Une note contient une information textuelle comme un commentaire, un corps de


méthode ou une contrainte. Graphiquement, elle est représentée par un rectangle dont
l'angle supérieur droit est plié. Le texte contenu dans le rectangle n'est pas contraint par
UML. Une note n'indique pas explicitement le type d'élément qu'elle contient, toute
l'intelligibilité d'une note doit être contenue dans le texte même. On peut relier une note
à l'élément qu'elle décrit grâce à une ligne en pointillés. Si elle décrit plusieurs éléments,
on dessine une ligne vers chacun d'entre eux.

L'exemple de la figure 1.11 montre une note exprimant une contrainte sur un attribut.

11
1.5 Modélisation des besoins avec UML

1.5.1 Comment identifier les acteurs ?

UML n'emploie pas le terme d'utilisateur, mais d'acteur. Les acteurs d'un système sont
les entités externes à ce système qui interagissent (saisie de données, réception
d'information…) avec lui. Les acteurs sont donc à l'extérieur du système et dialoguent
avec lui. Ces acteurs permettent de cerner l'interface que le système va devoir offrir à
son environnement. Oublier des acteurs ou en identifier de faux conduit donc
nécessairement à se tromper sur l'interface et donc la définition du système à produire.

Il faut faire attention à ne pas confondre acteurs et utilisateurs (utilisateur avec le sens
de la personne physique qui va appuyer sur un bouton) d'un système. D'une part parce
que les acteurs incluent les utilisateurs humains, mais aussi les autres systèmes
informatiques ou hardware qui vont communiquer avec le système. D'autre part parce
qu'un acteur englobe tout une classe d'utilisateurs. Ainsi, plusieurs utilisateurs peuvent
avoir le même rôle, et donc correspondre à un même acteur, et une même personne
physique peut jouer des rôles différents vis-à-vis du système, et donc correspondre à
plusieurs acteurs.

Chaque acteur doit être nommé. Ce nom doit refléter son rôle, car un acteur représente
un ensemble cohérent de rôles joués vis-à-vis du système.

Pour trouver les acteurs d'un système, il faut identifier quels sont les différents rôles que
vont devoir jouer ses utilisateurs (ex. : responsable clientèle, responsable d'agence,
administrateur, approbateur…). Il faut également s'intéresser aux autres systèmes avec
lesquels le système va devoir communiquer comme :

• les périphériques manipulés par le système (imprimantes, hardware d'un


distributeur de billets…) ;
• des logiciels déjà disponibles à intégrer dans le projet ;
• des systèmes informatiques externes au système, mais qui interagissent avec lui,
etc.

Pour faciliter la recherche des acteurs, on peut imaginer les frontières du système. Tout
ce qui est à l'extérieur et qui interagit avec le système est un acteur, tout ce qui est à
l'intérieur est une fonctionnalité à réaliser.

Vérifiez que les acteurs communiquent bien directement avec le système par émission ou
réception de messages. Une erreur fréquente consiste à répertorier en tant qu'acteur des
entités externes qui n'interagissent pas directement avec le système, mais uniquement
par le biais d'un des véritables acteurs. Par exemple, l'hôtesse de caisse d'un magasin de
grande distribution est un acteur pour la caisse enregistreuse, par contre, les clients du
magasin ne correspondent pas à un acteur, car ils n'interagissent pas directement avec la
caisse.

1.5.2 Comment recenser les cas d'utilisation ?

L'ensemble des cas d'utilisation doit décrire exhaustivement les exigences fonctionnelles
du système. Chaque cas d'utilisation correspond donc à une fonction métier du système,
selon le point de vue d'un de ses acteurs. Aussi, pour identifier les cas d'utilisation, il faut
se placer du point de vue de chaque acteur et déterminer comment et surtout pourquoi il
se sert du système. Il faut éviter les redondances et limiter le nombre de cas en se

12
situant à un bon niveau d'abstraction. Trouver le bon niveau de détail pour les cas
d'utilisation est un problème difficile qui nécessite de l'expérience.

Nommez les cas d'utilisation avec un verbe à l'infinitif suivi d'un complément en vous
plaçant du point de vue de l'acteur et non pas de celui du système. Par exemple, un
distributeur de billets aura probablement un cas d'utilisation Retirer de l'argent et non
pas Distribuer de l'argent.

De par la nature fonctionnelle, et non objet, des cas d'utilisation, et en raison de la


difficulté de trouver le bon niveau de détail, il faut être très vigilant pour ne pas retomber
dans une décomposition fonctionnelle descendante hiérarchique. Un nombre trop
important de cas d'utilisation est en général le symptôme de ce type d'erreur.

Dans tous les cas, il faut bien garder à l'esprit qu'il n'y a pas de notion temporelle dans
un diagramme de cas d'utilisation.

1.5.3 Remarques

Concernant les relations dans les cas d'utilisation

Il est important de noter que l'utilisation des relations n'est pas primordiale dans la
rédaction des cas d'utilisation et donc dans l'expression du besoin. Ces relations peuvent
être utiles dans certains cas, mais une trop forte focalisation sur leur usage conduit
souvent à une perte de temps ou à un usage faussé, pour une valeur ajoutée,
finalement, relativement faible.

Concernant les cas d'utilisation

Unanimement reconnus comme cantonnés à l'ingénierie des besoins, les diagrammes de


cas d'utilisation ne peuvent être qualifiés de modélisations à proprement parler.
D'ailleurs, de nombreux éléments descriptifs sont en langage naturel. De plus, ils ne
correspondent pas stricto sensu à une approche objet. En effet, capturer les besoins, les
découvrir, les réfuter, les consolider, etc., correspond plus à une analyse fonctionnelle
classique.

13
2 Diagramme de classes (Class Diagram)
2. Diagramme de classes (Class Diagram)............................................................... 14
2-1. Introduction .............................................................................................. 15
2-2. Les classes ................................................................................................ 15
2-2-1. Notions de classe et d'instance de classe ................................................ 15
2-2-2. Caractéristiques d'une classe ................................................................ 16
2-2-3. Représentation graphique ..................................................................... 17
2-2-4. Encapsulation, visibilité, interface .......................................................... 17
2-2-5. Nom d'une classe ................................................................................ 18
2-2-5-a. Métalangage des syntaxes .............................................................. 18
2-2-6. Les attributs ....................................................................................... 19
2-2-6-a. Attributs de la classe ..................................................................... 19
2-2-6-b. Attributs de classe ......................................................................... 19
2-2-6-c. Attributs dérivés ............................................................................ 19
2-2-7. Les méthodes...................................................................................... 20
2-2-7-a. Méthode de la classe...................................................................... 20
2-2-7-b. Méthode de classe ......................................................................... 20
2-2-7-c. Méthodes et classes abstraites ........................................................ 21
2-2-8. Classe active ....................................................................................... 21
2-3. Relations entre classes ............................................................................... 21
2-3-1. Notion d'association ............................................................................. 21
2-3-2. Terminaison d'association ..................................................................... 22
2-3-2-a. Propriétaire d'une terminaison d'association ..................................... 22
2-3-2-b. Une terminaison d'association est une propriété ................................ 23
2-3-3. Association binaire et n-aire .................................................................. 24
2-3-3-a. Association binaire......................................................................... 24
3-3-3-b. Association n-aire.......................................................................... 24
2-3-4. Multiplicité ou cardinalité ...................................................................... 24
2-3-5. Navigabilité......................................................................................... 25
2-3-6. Qualification ........................................................................................ 26
2-3-7. Classe-association ............................................................................... 27
2-3-7-a. Définition et représentation ............................................................ 27
2-3-7-b. Classe-association pour plusieurs associations .................................. 27
2-3-7-c. Autoassociation sur classe-association.............................................. 28
2-3-7-d. Liens multiples .............................................................................. 28
2-3-7-e. Équivalences................................................................................. 28
2-3-7-f. Classe-association, association n-aire ou association qualifiée ? ........... 29
2-3-8. Agrégation et composition .................................................................... 30
2-3-8-a. Agrégation ................................................................................... 30
2-3-8-b. Composition ................................................................................. 30

14
2-3-9. Généralisation et Héritage .................................................................... 31
2-3-10. Dépendance ...................................................................................... 32
2-4. Interfaces ................................................................................................. 32
2-5. Diagramme d'objets (object diagram)........................................................... 33
2-5-1. Présentation ....................................................................................... 33
2-5-2. Représentation .................................................................................... 34
2-5-3. Relation de dépendance d'instanciation .................................................. 35
2-6. Élaboration et implémentation d'un diagramme de classes .............................. 35
2-6-1. Élaboration d'un diagramme de classes .................................................. 35

2.1 Introduction
Le diagramme de classes est considéré comme le plus important de la modélisation
orientée objet, il est le seul obligatoire lors d'une telle modélisation.

Alors que le diagramme de cas d'utilisation montre un système du point de vue des
acteurs, le diagramme de classes en montre la structure interne. Il permet de fournir une
représentation abstraite des objets du système qui vont interagir pour réaliser les cas
d'utilisation. Il est important de noter qu'un même objet peut très bien intervenir dans la
réalisation de plusieurs cas d'utilisation. Les cas d'utilisation ne réalisent donc pas une
partition des classes du diagramme de classes. Un diagramme de classes n'est donc pas
adapté (sauf cas particulier) pour détailler, décomposer, ou illustrer la réalisation d'un
cas d'utilisation particulier.

Il s'agit d'une vue statique, car on ne tient pas compte du facteur temporel dans le
comportement du système. Le diagramme de classes modélise les concepts du domaine
d'application ainsi que les concepts internes créés de toutes pièces dans le cadre de
l'implémentation d'une application. Chaque langage de Programmation orienté objet
donne un moyen spécifique d'implémenter le paradigme objet (pointeurs ou pas, héritage
multiple ou pas, etc.), mais le diagramme de classes permet de modéliser les classes du
système et leurs relations indépendamment d'un langage de programmation particulier.

Les principaux éléments de cette vue statique sont les classes et leurs relations :
association, généralisation et plusieurs types de dépendances, telles que la réalisation et
l'utilisation.

2.2 Les classes

2.2.1 Notions de classe et d'instance de classe

Une instance est une concrétisation d'un concept abstrait. Par exemple :

• la Ferrari Enzo qui se trouve dans votre garage est une instance du concept
abstrait Automobile ;

15
• l'amitié qui lie Jean et Marie est une instance du concept abstrait Amitié ;

Une classe est un concept abstrait représentant des éléments variés comme :

• des éléments concrets (ex. : des avions),


• des éléments abstraits (ex. : des commandes de marchandises ou services),
• des composants d'une application (ex. : les boutons des boîtes de dialogue),
• des structures informatiques (ex. : des tables de hachage),
• des éléments comportementaux (ex. : des tâches), etc.

Tout système orienté objet est organisé autour des classes.

Une classe est la description formelle d'un ensemble d'objets ayant une sémantique et
des caractéristiques communes.

Un objet est une instance d'une classe. C'est une entité discrète dotée d'une identité,
d'un état et d'un comportement que l'on peut invoquer. Les objets sont des éléments
individuels d'un système en cours d'exécution.

Par exemple, si l'on considère que Homme (au sens être humain) est un concept abstrait,
on peut dire que la personne Marie-Cécile est une instance de Homme. Si Homme était
une classe, Marie-Cécile en serait une instance : un objet.

2.2.2 Caractéristiques d'une classe

Une classe définit un jeu d'objets dotés de caractéristiques communes. Les


caractéristiques d'un objet permettent de spécifier son état et son comportement.
Précédemment, nous avons dit que les caractéristiques d'un objet étaient soit des
attributs, soit des opérations. Ce n'est pas exact dans un diagramme de classe, car les
terminaisons d'associations sont des propriétés qui peuvent faire partie des
caractéristiques d'un objet au même titre que les attributs et les opérations.

État d'un objet :

• ce sont les attributs et généralement les terminaisons d'associations, tous deux


réunis sous le terme de propriétés structurelles, ou tout simplement propriétés1,
qui décrivent l'état d'un objet. Les attributs sont utilisés pour des valeurs de
données pures, dépourvues d'identité, telles que les nombres et les chaînes de
caractères. Les associations sont utilisées pour connecter les classes du
diagramme de classe ; dans ce cas, la terminaison de l'association (du côté de la
classe cible) est généralement une propriété de la classe de base.

Les propriétés décrites par les attributs prennent des valeurs lorsque la classe est
instanciée. L'instance d'une association est appelée un lien.

1
Il faut ici aborder un petit problème de terminologie autour du mot propriété. En effet, dans la littérature, le
mot propriété est parfois utilisé pour désigner toutes les caractéristiques d'une classe (i.e. les attributs comme
les méthodes). Dans ce cas, les attributs et les terminaisons d'association sont rassemblés sous le terme de
propriétés structurelles, le qualificatif structurel prenant ici toute son importance. D'un autre côté, le mot
propriété est souvent utilisé dans l'acception du terme anglais property (dans la norme UML Superstructure
version 2.1.1), qui, lui, ne désigne que les attributs et les terminaisons d'association, c'est-à-dire les propriétés
structurelles. Pour englober les méthodes, il faut alors utiliser le terme plus générique de caractéristiques, qui
prend ainsi le rôle de traduction du terme anglais feature dans la norme. Dans le présent cours, je m'efforce de
me conformer à cette deuxième solution où propriété et propriété structurelle désignent finalement la même
chose.

16
Comportement d'un objet :

• les opérations décrivent les éléments individuels d'un comportement que l'on peut
invoquer. Ce sont des fonctions qui peuvent prendre des valeurs en entrée et
modifier les attributs ou produire des résultats.

Une opération est la spécification (i.e. déclaration) d'une méthode.


L'implémentation (i.e. définition) d'une méthode est également appelée méthode.
Il y a donc une ambiguïté sur le terme méthode.

Les attributs, les terminaisons d'association et les méthodes constituent donc les
caractéristiques d'une classe (et de ses instances).

2.2.3 Représentation graphique

Figure 2.1 : Représentation UML d'une classe.

Une classe est représentée par un rectangle divisé en trois à cinq compartiments (figure
2.1).

Le premier indique le nom de la classe (cf. section 2.2.5), le deuxième ses attributs (cf.
section 2.2.6) et le troisième ses opérations (cf. section 2.2.7). Un compartiment des
responsabilités peut être ajouté pour énumérer l'ensemble de tâches devant être
assurées par la classe, mais pour lesquelles on ne dispose pas encore assez
d'informations. Un compartiment des exceptions peut également être ajouté pour
énumérer les situations exceptionnelles devant être gérées par la classe.

2.2.4 Encapsulation, visibilité, interface

Figure 2.2 : Bonnes pratiques concernant la manipulation des attributs.

L'encapsulation est un mécanisme consistant à rassembler les données et les méthodes


au sein d'une structure en cachant l'implémentation de l'objet, c'est-à-dire en empêchant
l'accès aux données par un autre moyen que les services proposés. Ces services

17
accessibles (offerts) aux utilisateurs de l'objet définissent ce que l'on appelle l'interface
de l'objet (sa vue externe). L'encapsulation permet donc de garantir l'intégrité des
données contenues dans l'objet.

L'encapsulation permet de définir des niveaux de visibilité des éléments d'un conteneur.
La visibilité déclare la possibilité pour un élément de modélisation de référencer un
élément qui se trouve dans un espace de noms différent de celui de l'élément qui établit
la référence. Elle fait partie de la relation entre un élément et le conteneur qui l'héberge,
ce dernier pouvant être un paquetage, une classe ou un autre espace de noms. Il existe
quatre visibilités prédéfinies.

Public ou + :

• tout élément qui peut voir le conteneur peut également voir l'élément indiqué.

Protected ou # :

• seul un élément situé dans le conteneur ou un de ses descendants peut voir


l'élément indiqué.

Private ou - :

• seul un élément situé dans le conteneur peut voir l'élément.

Package ou ∼ ou rien :

• seul un élément déclaré dans le même paquetage peut voir l'élément.

Dans une classe, le marqueur de visibilité se situe au niveau de chacune de ses


caractéristiques (attributs, terminaisons d'association et opération). Il permet d'indiquer
si une autre classe peut y accéder.

Dans la pratique, lorsque des attributs doivent être accessibles de l'extérieur, il est
préférable que cet accès ne soit pas direct, mais se fasse par l'intermédiaire d'opérations.

2.2.5 Nom d'une classe

Le nom de la classe doit évoquer le concept décrit par la classe. Il commence par une
majuscule. On peut ajouter des informations subsidiaires comme le nom de l'auteur de la
modélisation, la date, etc. Pour indiquer qu'une classe est abstraite, il faut ajouter le
mot-clef abstract.

Métalangage des syntaxes

Nous aurons régulièrement recours à ce métalangage pour décrire des syntaxes de


déclaration. Ce métalangage contient certains métacaractères :

[]:

• les crochets indiquent que ce qui est à l'intérieur est optionnel ;

18
<>:

• les signes inférieur et supérieur indiquent que ce qui est à l'intérieur est plus ou
moins libre ; par exemple, la syntaxe de déclaration d'une variable comme
compteur : int est <nom_variable> : <type> ;

'':

• les cotes sont utiles quand on veut utiliser un métacaractère comme un


caractère ; par exemple, pour désigner un crochet ([) il faut écrire '[», car [ est
un métacaractère ayant une signification spéciale ;

…:

• permet de désigner une suite de séquence de longueur non définie, le contexte


permettant de comprendre de quelle suite il s'agit.

2.2.6 Les attributs

Attributs de la classe

Les attributs définissent des informations qu'une classe ou un objet doivent connaître. Ils
représentent les données encapsulées dans les objets de cette classe. Chacune de ces
informations est définie par un nom, un type de données, une visibilité et peut être
initialisée. Le nom de l'attribut doit être unique dans la classe. La syntaxe de la
déclaration d'un attribut est la suivante :

<visibilité> <nom_attribut> : <type>

Le type de l'attribut (<type>) peut être un nom de classe, un nom d'interface ou un type
de donnée prédéfini.

Attributs de classe

Par défaut, chaque instance d'une classe possède sa propre copie des attributs de la
classe. Les valeurs des attributs peuvent donc différer d'un objet à un autre. Cependant,
il est parfois nécessaire de définir un attribut de classe (static en Java ou en C++) qui
garde une valeur unique et partagée par toutes les instances de la classe. Les instances
ont accès à cet attribut, mais n'en possèdent pas une copie. Un attribut de classe n'est
donc pas une propriété d'une instance, mais une propriété de la classe et l'accès à cet
attribut ne nécessite pas l'existence d'une instance.

Graphiquement, un attribut de classe (donc un attribut statique) est souligné.

Attributs dérivés

Les attributs dérivés peuvent être calculés à partir d'autres attributs et de formules de
calcul. Lors de la conception, un attribut dérivé peut être utilisé comme marqueur jusqu'à
ce que vous puissiez déterminer les règles à lui appliquer.

Les attributs dérivés sont symbolisés par l'ajout d'un « / » devant leur nom.

19
2.2.7 Les méthodes

Méthode de la classe

Dans une classe, une opération (même nom et mêmes types de paramètres) doit être
unique. Quand le nom d'une opération apparaît plusieurs fois avec des paramètres
différents, on dit que l'opération est surchargée. En revanche, il est impossible que deux
opérations ne se distinguent que par leur valeur retournée.

La déclaration d'une opération contient les types des paramètres et le type de la valeur
de retour, sa syntaxe est la suivante :

<visibilité> <nom_méthode> ([<paramètre_1>, ... , <paramètre_N>]) :


[<type_renvoyé>] [{<propriétés>}]

La syntaxe de définition d'un paramètre (<paramètre>) est la suivante :

[<direction>] <nom_paramètre> : <type> [=<valeur_par_défaut>]

La direction peut prendre l'une des valeurs suivantes :

in :

• paramètre d'entrée passé par valeur. Les modifications du paramètre ne sont pas
disponibles pour l'appelant. C'est le comportement par défaut.

out :

• paramètre de sortie uniquement. Il n'y a pas de valeur d'entrée et la valeur finale


est disponible pour l'appelant.

inout :

• paramètre d'entrée/sortie. La valeur finale est disponible pour l'appelant.

Le type du paramètre (<type>) peut être un nom de classe, un nom d'interface ou un


type de donnée prédéfini.

Les propriétés (<propriétés>) correspondent à des contraintes ou à des informations


complémentaires comme les exceptions, les préconditions, les postconditions ou encore
l'indication qu'une méthode est abstraite (mot-clef abstract), etc.

Méthode de classe

Comme pour les attributs de classe, il est possible de déclarer des méthodes de classe.
Une méthode de classe ne peut manipuler que des attributs de classe et ses propres
paramètres. Cette méthode n'a pas accès aux attributs de la classe (i.e. des instances de
la classe). L'accès à une méthode de classe ne nécessite pas l'existence d'une instance
de cette classe.

Graphiquement, une méthode de classe est soulignée.

20
Méthodes et classes abstraites

Une méthode est dite abstraite lorsqu'on connaît son entête, mais pas la manière dont
elle peut être réalisée (i.e. on connaît sa déclaration, mais pas sa définition).

Une classe est dite abstraite lorsqu'elle définit au moins une méthode abstraite ou
lorsqu'une classe parent (cf. section 2.3.9) contient une méthode abstraite non encore
réalisée.

On ne peut instancier une classe abstraite : elle est vouée à se spécialiser (cf. section
2.3.9). Une classe abstraite peut très bien contenir des méthodes concrètes.

Une classe abstraite pure ne comporte que des méthodes abstraites. En programmation
orientée objet (par exemple, en java), une telle classe est appelée une interface.

Pour indiquer qu'une classe est abstraite, il faut ajouter le mot-clef abstract derrière son
nom.

2.2.8 Classe active

Une classe est passive par défaut, elle sauvegarde les données et offre des services aux
autres. Une classe active initie et contrôle le flux d'activités.

Graphiquement, une classe active est représentée comme une classe standard dont les
lignes verticales du cadre, sur les côtés droit et gauche, sont doublées.

2.3 Relations entre classes

2.3.1 Notion d'association

Une association est une relation entre deux classes (association binaire) ou plus
(association n-aire), qui décrit les connexions structurelles entre leurs instances. Une
association indique donc qu'il peut y avoir des liens entre des instances des classes
associées.

Comment une association doit-elle être modélisée ? Plus précisément, quelle différence
existe-t-il entre les deux diagrammes de la figure 2.3 ?

21
Figure 2.3 : Deux façons de modéliser une association.

Dans la première version, l'association apparaît clairement et constitue une entité


distincte. Dans la seconde, l'association se manifeste par la présence de deux attributs
dans chacune des classes en relation. C'est en fait la manière dont une association est
généralement implémentée dans un langage objet quelconque, mais pas dans tout
langage de représentation (par exemple, SQL).

La question de savoir s'il faut modéliser les associations en tant que telles a longtemps
fait débat. UML a tranché pour la première version, car elle se situe plus à un niveau
conceptuel (par opposition au niveau d'implémentation) et simplifie grandement la
modélisation d'associations complexes (comme les associations plusieurs à plusieurs par
exemple).

Un attribut peut alors être considéré comme une association dégénérée dans laquelle une
terminaison d'association2 est détenue par un classeur (généralement une classe). Le
classeur détenant cette terminaison d'association devrait théoriquement se trouver à
l'autre extrémité, non modélisée, de l'association. Un attribut n'est donc rien d'autre
qu'une terminaison d'un cas particulier d'association (cf. figure 2.9 section 2.3.5).

Ainsi, les terminaisons d'associations et les attributs sont deux éléments


conceptuellement très proches que l'on regroupe sous le terme de propriété.

2.3.2 Terminaison d'association

Propriétaire d'une terminaison d'association

La possession d'une terminaison d'association par le classeur situé à l'autre extrémité de


l'association peut être spécifiée graphiquement par l'adjonction d'un petit cercle plein
(point) entre la terminaison d'association et la classe (cf. figure 2.4). Il n'est pas
indispensable de préciser la possession des terminaisons d'associations. Dans ce cas, la
possession est ambiguë. Par contre, si l'on indique des possessions de terminaisons
d'associations, toutes les terminaisons d'associations sont non ambiguës : la présence
d'un point implique que la terminaison d'association appartient à la classe située à l'autre
extrémité, l'absence du point implique que la terminaison d'association appartient à
l'association.

2
Une terminaison d'associations est une extrémité de l'association. Une association binaire en possède deux,
une association n-aire en possède n.

22
Figure 2.4 : Utilisation d'un petit cercle plein pour préciser le propriétaire d'une
terminaison d'association.

Par exemple, le diagramme de la figure 2.4 précise que la terminaison d'association


sommet (i.e. la propriété sommet) appartient à la classe Polygone tandis que la
terminaison d'association polygone (i.e. la propriété polygone) appartient à l'association
Défini par.

En SQL, le diagramme de la figure 2.4 spécifie que, dans la table Polygone, il y aurait un
champ permettant de se référer à trois ou plus instances de Point (donc, la relation est
explicitée à l’intérieur de la table Polygone). Sans le point, il serait possible de modéliser
cette association par un tableau associatif (donc, rien directement dans les tables
Polygone et Point).

Une terminaison d'association est une propriété

Une propriété est une caractéristique structurelle. Dans le cas d'une classe, les propriétés
sont constituées par les attributs et les éventuelles terminaisons d'association que
possède la classe. Dans le cas d'une association, les propriétés sont constituées par les
terminaisons d'association que possède l'association.

Attention, une association ne possède pas forcément toutes ses terminaisons


d'association !

Une propriété peut être paramétrée par les éléments suivants (on s'intéresse ici
essentiellement aux terminaisons d'associations puisque les attributs ont été largement
traités précédemment) :

nom :

• comme un attribut, une terminaison d'association peut être nommée. Le nom est
situé à proximité de la terminaison, mais contrairement à un attribut, ce nom est
facultatif. Le nom d'une terminaison d'association est appelé nom du rôle. Une
association peut donc posséder autant de noms de rôle que de terminaisons (deux
pour une association binaire et n pour une association n-aire) ;

visibilité :

• comme un attribut, une terminaison d'association possède une visibilité. La


visibilité est mentionnée à proximité de la terminaison, et plus précisément, le cas
échéant, devant le nom de la terminaison ;

multiplicité :

• une terminaison d'association peut posséder une multiplicité. Elle est mentionnée
à proximité de la terminaison. Il n'est pas impératif de la préciser, mais la
multiplicité par défaut d'une terminaison d'association est non spécifiée.
navigabilité :

• pour un attribut, la navigabilité est implicite, navigable, et toujours depuis la


classe vers l'attribut. Pour une terminaison d'association, la navigabilité peut être
précisée (cf. section 2.3.5).

23
2.3.3 Association binaire et n-aire

Association binaire

Figure 2.5 : Exemple d'association binaire.

Une association binaire est matérialisée par un trait plein entre les classes associées (cf.
figure 2.5). Elle peut être ornée d'un nom, avec éventuellement une précision du sens de
lecture (▸ ou ◂).

Quand les deux extrémités de l'association pointent vers la même classe, l'association est
dite réflexive.

Association n-aire

Figure 2.6 : Exemple d'association n-aire.

Une association n-aire lie plus de deux classes. La section 2.3.4 détaille comment
interpréter les multiplicités d'une association n-aire. La ligne pointillée d'une classe-
association (cf. section 2.3.7) peut être reliée au losange par une ligne discontinue pour
représenter une association n-aire dotée d'attributs, d'opérations ou d'associations.

On représente une association n-aire par un grand losange avec un chemin partant vers
chaque classe participante (cf. figure 2.6). Le nom de l'association, le cas échéant,
apparaît à proximité du losange.

2.3.4 Multiplicité ou cardinalité

La multiplicité associée à une terminaison d'association, d'agrégation ou de composition


déclare le nombre d'objets susceptibles d'occuper la position définie par la terminaison
d'association. Voici quelques exemples de multiplicité :

• exactement un : 1 ou 1..1 ;
• plusieurs : * ou 0..* ;
• au moins un : 1..* ;
• de un à six : 1..6.

Dans une association binaire (cf. figure 2.5), la multiplicité sur la terminaison cible
contraint le nombre d'objets de la classe cible pouvant être associés à un seul objet
donné de la classe source (la classe de l'autre terminaison de l'association).

Dans une association n-aire, la multiplicité apparaissant sur le lien de chaque classe
s'applique sur une instance de chacune des classes, à l'exclusion de la classe-association

24
et de la classe considérée. Par exemple, si on prend une association ternaire entre les
classes (A, B, C), la multiplicité de la terminaison C indique le nombre d'objets C qui
peuvent apparaître dans l'association (définie section 2.3.7) avec une paire particulière
d'objets A et B.

Pour une association n-aire, la multiplicité minimale doit en principe, mais pas
nécessairement, être 0. En effet, une multiplicité minimale de 1 (ou plus) sur une
extrémité implique qu'il doit exister un lien (ou plus) pour TOUTES les combinaisons
possibles des instances des classes situées aux autres extrémités de l'association n-aire !

Il faut noter que, pour les habitués du modèle entité/relation (utilisé en SQL), les
multiplicités sont en UML « à l'envers » (par référence à Merise) pour les associations
binaires et « à l'endroit » pour les n-aires avec n>2.

2.3.5 Navigabilité

Figure 2.7 : Navigabilité.

La navigabilité indique s'il est possible de traverser une association. On représente


graphiquement la navigabilité par une flèche du côté de la terminaison navigable et on
empêche la navigabilité par une croix du côté de la terminaison non navigable (cf. figure
2.7). Par défaut, une association est navigable dans les deux sens.

Par exemple, sur la figure 2.7, la terminaison du côté de la classe Commande n'est pas
navigable : cela signifie que les instances de la classe Produit ne stockent pas de liste
d'objets du type Commande. Inversement, la terminaison du côté de la classe Produit est
navigable : chaque objet commande contient une liste de produits.

Figure 2.8 : Implicitement, ces trois notations ont la même sémantique.

Lorsque l'on représente la navigabilité uniquement sur l'une des extrémités d'une
association, il faut remarquer que, implicitement, les trois associations représentées sur
la figure 2.8 ont la même signification : l'association ne peut être traversée que dans un
sens.

25
Figure 2.9 : Deux modélisations équivalentes.

2.3.6 Qualification

Figure 2.10 : En haut, un diagramme représentant l'association entre une banque et ses
clients (à gauche), et un diagramme représentant l'association entre un échiquier et les
cases qui le composent (à droite). En bas, les diagrammes équivalents utilisant des
associations qualifiées.

Généralement, une classe peut être décomposée en sous-classes ou posséder plusieurs


propriétés. Une telle classe rassemble un ensemble d'éléments (d'objets). Quand une
classe est liée à une autre classe par une association, il est parfois préférable de
restreindre la portée de l'association à quelques éléments ciblés (comme un ou plusieurs
attributs) de la classe. Ces éléments ciblés sont appelés un qualificatif. Un qualificatif
permet donc de sélectionner un ou des objets dans le jeu des objets d'un objet (appelé
objet qualifié) relié par une association à un autre objet. L'objet sélectionné par la valeur
du qualificatif est appelé objet cible. L'association est appelée association qualifiée. Un
qualificatif agit toujours sur une association dont la multiplicité est plusieurs (avant que
l'association ne soit qualifiée) du côté cible.

Un objet qualifié et une valeur de qualificatif génèrent un objet cible lié unique. En
considérant un objet qualifié, chaque valeur de qualificatif désigne un objet cible unique.

Par exemple, le diagramme de gauche de la figure 2.10 nous dit que :

• un compte dans une banque appartient à au plus deux personnes. Autrement dit,
une instance du couple {Banque , compte} est en association avec zéro à deux
instances de la classe Personne ;

26
• mais une personne peut posséder plusieurs comptes dans plusieurs banques.
C'est-à-dire qu'une instance de la classe Personne peut être associée à plusieurs
(zéro compris) instances du couple {Banque , compte} ;
• bien entendu, et dans tous les cas, une instance du couple {Personne , compte}
est en association avec une instance unique de la classe Banque.

Le diagramme de droite de cette même figure nous dit que :

• une instance du triplet {Échiquier, rangée, colonne} est en association avec une
instance unique de la classe Case ;
• inversement, une instance de la classe Case est en association avec une instance
unique du triplet {Échiquier, rangée, colonne}.

2.3.7 Classe-association

Définition et représentation

Figure 2.11 : Exemple de classe-association.

Parfois, une association doit posséder des propriétés. Par exemple, l'association Emploie
entre une société et une personne possède comme propriétés le salaire et la date
d'embauche. En effet, ces deux propriétés n'appartiennent ni à la société, qui peut
employer plusieurs personnes, ni aux personnes, qui peuvent avoir plusieurs emplois. Il
s'agit donc bien de propriétés de l'association Emploie. Les associations ne pouvant
posséder de propriété, il faut introduire un nouveau concept pour modéliser cette
situation : celui de classe-association.

Une classe-association possède les caractéristiques des associations et des classes : elle
se connecte à deux ou plusieurs classes et possède également des attributs et des
opérations.

Une classe-association est caractérisée par un trait discontinu entre la classe et


l'association qu'elle représente (figure 2.11).

Classe-association pour plusieurs associations

Il n'est pas possible de rattacher une classe-association à plus d'une association puisque
la classe-association constitue elle-même l'association. Dans le cas où plusieurs classe-
associations doivent disposer des mêmes caractéristiques, elles doivent hériter d'une
même classe possédant ces caractéristiques, ou l'utiliser en tant qu'attribut.

27
Autoassociation sur classe-association

Figure 2.12 : Exemple d'autoassociation sur classe-association.

Imaginons que nous voulions ajouter une association Supérieur de dans le diagramme
2.11 pour préciser qu'une personne est le supérieur d'une autre personne. On ne peut
simplement ajouter une association réflexive sur la classe Personne. En effet, une
personne n'est pas le supérieur d'une autre dans l'absolu. Une personne est, en tant
qu'employé d'une entreprise donnée, le supérieur d'une autre personne dans le cadre de
son emploi pour une entreprise donnée (généralement, mais pas nécessairement, la
même). Il s'agit donc d'une association réflexive, non pas sur la classe Personne, mais
sur la classe-association Emploie (cf. figure 2.12).

Liens multiples

Figure 2.13 : Exemple de classe-association avec liens multiples.

Plusieurs instances d'une même association ne peuvent lier les mêmes objets.
Cependant, si l'on s'intéresse à l'exemple de la figure 2.13, on voit bien qu'il doit pouvoir
y avoir plusieurs instances de la classe-association Actions liant une même personne à
une même société : une même personne peut acheter à des moments différents des
actions d'une même société.

C'est justement la raison de la contrainte {bag} qui, placée sur les terminaisons
d'association de la classe-association Actions, indique qu'il peut y avoir des liens
multiples impliquant les mêmes paires d'objets.

Équivalences

Parfois, l'information véhiculée par une classe-association peut être véhiculée sans perte
d'une autre manière (cf. figure 3.14 et 3.15).

28
Figure 2.14 : Deux modélisations modélisant la même information.

Figure 2.15 : Deux modélisations modélisant la même information.

Classe-association, association n-aire ou association qualifiée ?

Il n'est souvent pas simple trancher entre l'utilisation d'une classe-association, d'une
association n-aire ou encore d'une association qualifiée. Lorsque l'on utilise l'un de ces
trois types d'association, il peut être utile ou instructif de se demander si l'un des deux
autres types ne serait pas plus pertinent. Dans tous les cas, il faut garder à l'esprit
qu'une classe-association est d'abord et avant tout une association et que, dans une
classe-association, la classe est indissociable de l'association.

Figure 2.16 : Pour couvrir le cas des comptes joints, il faut utiliser le modèle de droite.

Ainsi, le cas d'un compte joint ne peut être représenté correctement par le diagramme de
gauche dans figure 2.16 : mieux vaut utiliser une association qualifiée (diagramme de
droite dans la figure ). Ce problème et à nouveau abordé et illustré section 2.5.2.

Figure 2.17 : Si un cours doit pouvoir exister indépendamment d'un lien entre un
enseignant et un groupe, il faut utiliser le modèle de droite.

29
Dans le diagramme de gauche de la figure 2.17, un cours ne peut exister que s'il existe
un lien entre un objet Enseignant et un objet Groupe. Quand le lien est rompu (effacé),
le cours l'est également. Si un cours doit pouvoir exister indépendamment de l'existence
d'un lien (on n'a pas encore trouvé d'enseignant pour ce cours, le cours n'est pas
enseigné cette année, mais le sera probablement l'année prochaine…) il faut opter pour
une association ternaire (modèle de droite dans figure 2.17).

Figure 2.18 : Si un même cours doit concerner plusieurs couples Enseignant/Étudiant, il


ne faut pas utiliser une classe-association, mais une association ternaire comme sur le
modèle de droite.

Le cas de figure est encore pire si l'on remplace Groupe par Étudiant (cf. modèle à
gauche sur la figure 2.18). En effet, le cas général décrit par ce modèle ne correspond
pas du tout au diagramme d'objet situé au centre. Nous reviendrons sur ce problème
dans la section 2.5.2 avec l'illustration 2.24. Dans cette situation, il faut opter pour une
association ternaire comme sur le modèle de droite.

2.3.8 Agrégation et composition

Agrégation

Figure 2.19 : Exemple de relation d'agrégation et de composition.

Une association simple entre deux classes représente une relation structurelle entre
pairs, c'est-à-dire entre deux classes de même niveau conceptuel : aucune des deux
n'est plus importante que l'autre. Lorsque l'on souhaite modéliser une relation tout/partie
où une classe constitue un élément plus grand (tout) composé d'éléments plus petits
(partie), il faut utiliser une agrégation.

Une agrégation est une association qui représente une relation d'inclusion structurelle ou
comportementale d'un élément dans un ensemble. Graphiquement, on ajoute un losange
vide du côté de l'agrégat (cf. figure 2.19). Contrairement à une association simple,
l'agrégation est transitive.

La signification de cette forme simple d'agrégation est uniquement conceptuelle. Elle ne


contraint pas la navigabilité ou les multiplicités de l'association. Elle n'entraîne pas non
plus de contrainte sur la durée de vie des parties par rapport au tout.

Composition

30
La composition, également appelée agrégation composite, décrit une contenance
structurelle entre instances. Ainsi, la destruction de l'objet composite implique la
destruction de ses composants. Une instance de la partie appartient toujours à au plus
une instance de l'élément composite : la multiplicité du côté composite ne doit pas être
supérieure à 1 (i.e. 1 ou 0..1). Graphiquement, on ajoute un losange plein du côté de
l'agrégat (cf. figure 2.19).

Les notions d'agrégation et surtout de composition posent de nombreux problèmes en


modélisation et sont souvent le sujet de querelles d'experts et sources de confusions. Ce
que dit la norme UML Superstructure version 2.1.1 reflète d'ailleurs très bien le flou qui
entoure ces notions : Precise semantics of shared aggregation varies by application area
and modeler. The order and way in which part instances are created is not defined.

2.3.9 Généralisation et Héritage

Figure 2.20 : Partie du règne animal décrit avec l'héritage multiple.

La généralisation décrit une relation entre une classe générale (classe de base ou classe
parent) et une classe spécialisée (sous-classe). La classe spécialisée est intégralement
cohérente avec la classe de base, mais comporte des informations supplémentaires
(attributs, opérations, associations). Un objet de la classe spécialisée peut être utilisé
partout où un objet de la classe de base est autorisé.

Dans le langage UML, ainsi que dans la plupart des langages objet, cette relation de
généralisation se traduit par le concept d'héritage. On parle également de relation
d'héritage. Ainsi, l'héritage permet la classification des objets (cf. figure 2.20).

Le symbole utilisé pour la relation d'héritage ou de généralisation est une flèche avec un
trait plein dont la pointe est un triangle fermé désignant le cas le plus général (cf. figure
2.20).

Les propriétés principales de l'héritage sont :

• la classe enfant possède toutes les caractéristiques de ses classes parents, mais
elle ne peut accéder aux caractéristiques privées de cette dernière ;
• une classe enfant peut redéfinir (même signature) une ou plusieurs méthodes de
la classe parent. Sauf indication contraire, un objet utilise les opérations les plus
spécialisées dans la hiérarchie des classes ;
• toutes les associations de la classe parent s'appliquent aux classes dérivées ;
• une instance d'une classe peut être utilisée partout où une instance de sa classe
parent est attendue. Par exemple, en se basant sur le diagramme de la figure

31
2.20, toute opération acceptant un objet d'une classe Animal doit accepter un
objet de la classe Chat ;
• une classe peut avoir plusieurs parents, on parle alors d'héritage multiple (cf. la
classe Ornithorynque de la figure 2.20). Le langage C++ est un des langages
objet permettant son implémentation effective, le langage Java ne le permet pas.

En UML, la relation d'héritage n'est pas propre aux classes. Elle s'applique à d'autres
éléments du langage comme les paquetages, les acteurs ou les cas d’utilisation.

2.3.10Dépendance

Figure 2.21 : Exemple de relation de dépendance.

Une dépendance est une relation unidirectionnelle exprimant une dépendance


sémantique entre des éléments du modèle. Elle est représentée par un trait discontinu
orienté (cf. figure 2.21). Elle indique que la modification de la cible peut impliquer une
modification de la source. La dépendance est souvent stéréotypée pour mieux expliciter
le lien sémantique entre les éléments du modèle (cf. figure 2.21 ou 2.25).

On utilise souvent une dépendance quand une classe en utilise une autre comme
argument dans la signature d'une opération. Par exemple, le diagramme de la figure 2.21
montre que la classe Confrontation utilise la classe Stratégie, car la classe Confrontation
possède une méthode confronter dont deux paramètres sont du type Stratégie. Si la
classe Stratégie, notamment son interface, change, alors des modifications devront
également être apportées à la classe Confrontation.

2.4 Interfaces

Figure 2.22 : Exemple de diagramme mettant en œuvre une interface.

32
Nous avons déjà abordé la notion d'interface dans la section 2.2.4. En effet, les classes
permettent de définir en même temps un objet et son interface. Le classeur, que nous
décrivons dans cette section, ne permet de définir que des éléments d'interface. Il peut
s'agir de l'interface complète d'un objet, ou simplement d'une partie d'interface qui sera
commune à plusieurs objets.

Le rôle de ce classeur, stéréotypé << interface >>, est de regrouper un ensemble de


propriétés et d'opérations assurant un service cohérent. L'objectif est de diminuer le
couplage entre deux classeurs. La notion d'interface en UML est très proche de la notion
d'interface en Java.

Une interface est représentée comme une classe excepté l'absence du mot-clef abstract
(car l'interface et toutes ses méthodes sont, par définition, abstraites) et l'ajout du
stéréotype << interface >> (cf. figure 2.22).

Une interface doit être réalisée par au moins une classe et peut l'être par plusieurs.
Graphiquement, cela est représenté par un trait discontinu terminé par une flèche
triangulaire et le stéréotype « realize ». Une classe peut très bien réaliser plusieurs
interfaces. Une classe (classe cliente de l'interface) peut dépendre d'une interface
(interface requise). On représente cela par une relation de dépendance et le stéréotype
« use ».

Attention aux problèmes de conflits si une classe dépend d'une interface réalisée par
plusieurs autres classes.

La notion d'interface est assez proche de la notion de classe abstraite avec une capacité
de découplage plus grand. En C++ (le C++ ne connaît pas la notion d'interface), la
notion d'interface est généralement implémentée par une classe abstraite.

2.5 Diagramme d'objets (object diagram)

2.5.1 Présentation

Un diagramme d'objets représente des objets (i.e. instances de classes) et leurs liens
(i.e. instances de relations) pour donner une vue figée de l'état d'un système à un instant
donné. Un diagramme d'objets peut être utilisé pour :

• illustrer le modèle de classes en montrant un exemple qui explique le modèle ;


• préciser certains aspects du système en mettant en évidence des détails
imperceptibles dans le diagramme de classes ;
• exprimer une exception en modélisant des cas particuliers ou des connaissances
non généralisables qui ne sont pas modélisés dans un diagramme de classe ;
• prendre une image (snapshot) d'un système à un moment donné.

Le diagramme de classes modélise les règles et le diagramme d'objets modélise des faits.

Par exemple, le diagramme de classes de la figure 2.23 montre qu'une entreprise


emploie au moins deux personnes et qu'une personne travaille dans au plus deux
entreprises. Le diagramme d'objets modélise lui une entreprise particulière (PERTNE) qui
emploie trois personnes. Un diagramme d'objets ne montre pas l'évolution du système
dans le temps. Pour représenter une interaction, il faut utiliser un diagramme de ou de
séquence.

33
2.5.2 Représentation

Figure 2.23 : Exemple de diagramme de classes et de diagramme d'objets associé.

Graphiquement, un objet se représente comme une classe. Cependant, le compartiment


des opérations n'est pas utile. De plus, le nom de la classe dont l'objet est une instance
est précédé d'un << : >> et est souligné. Pour différencier les objets d'une même
classe, leur identifiant peut être ajouté devant le nom de la classe. Enfin les attributs
reçoivent des valeurs. Quand certaines valeurs d'attribut d'un objet ne sont pas
renseignées, on dit que l'objet est partiellement défini.

Dans un diagramme d'objets, les relations du diagramme de classes deviennent des


liens. La relation de généralisation ne possède pas d'instance, elle n'est donc jamais
représentée dans un diagramme d'objets. Graphiquement, un lien se représente comme
une relation, mais, s'il y a un nom, il est souligné. Naturellement, on ne représente pas
les multiplicités.

Figure 2.24 : Le diagramme d'objets de droite, illustrant le cas de figure d'un compte
joint, n'est pas une instance normale du diagramme de classe de gauche, mais peut
préciser une situation exceptionnelle.

La norme UML 2.1.1 précise qu'une instance de classe-association ne peut être associée
qu'à une instance de chacune des classes associées ce qui interdit d'instancier le
diagramme de classe à gauche dans la figure 2.24 par le diagramme d'objet à droite
dans cette même figure. Cependant, un diagramme d'objet peut être utilisé pour
exprimer une exception. Sur la figure , le diagramme d'objets à droite peut être légitime
s'il vient préciser une situation exceptionnelle non prise en compte par le diagramme de
classe représenté à gauche. Néanmoins, le cas des comptes joints n'étant pas si
exceptionnel, mieux vaut revoir la modélisation comme préconisé par la figure 2.16.

34
2.5.3 Relation de dépendance d'instanciation

Figure 2.25 : Dépendance d'instanciation entre les classeurs et leurs instances.

La relation de dépendance d'instanciation (stéréotypée << instanceof >>) décrit la


relation entre un classeur et ses instances. Elle relie, en particulier, les liens aux
associations et les objets aux classes.

2.6 Élaboration et implémentation d'un diagramme de


classes

2.6.1 Élaboration d'un diagramme de classes

Une démarche couramment utilisée pour bâtir un diagramme de classes consiste à :

Trouver les classes du domaine étudié. Cette étape empirique se fait


généralement en collaboration avec un expert du domaine. Les classes correspondent
généralement à des concepts ou des substantifs du domaine ;

Trouver les associations entre classes. Les associations correspondent


souvent à des verbes, ou des constructions verbales, mettant en relation plusieurs
classes, comme << est composé de >>, << pilote >>, << travaille pour >>.

Attention, méfiez-vous de certains attributs qui sont en réalité des relations entre
classes.

Trouver les attributs des classes. Les attributs correspondent souvent à des
substantifs, ou des groupes nominaux, tels que << la masse d'une voiture >> ou << le
montant d'une transaction >>.
Les adjectifs et les valeurs correspondent souvent à des valeurs d'attributs. Vous pouvez
ajouter des attributs à toutes les étapes du cycle de vie d'un projet (implémentation
comprise). N'espérez pas trouver tous les attributs dès la construction du diagramme de
classes ;

Organiser et simplifier le modèle, en éliminant les classes redondantes et en


utilisant l'héritage ;

Itérer et raffiner le modèle. Un modèle est rarement correct dès sa première


construction. La modélisation objet est un processus non pas linéaire, mais itératif.

35
3 Diagramme d'états-transitions (State machine
diagram)
3 Diagramme d'états-transitions (State machine diagram) .................................... 36
3.1 Introduction au formalisme ...................................................................... 37
3.1.1 Présentation .................................................................................... 37
3.1.2 Notion d'automate à états finis▲........................................................ 37
3.1.3 Les deux acceptions du terme état ..................................................... 38
État dans un diagramme d'états-transitions................................................... 38
État d'un objet, ou du diagramme d'états-transitions ...................................... 38
3.1.4 État initial et final............................................................................. 39
État initial ................................................................................................. 39
État final ................................................................................................... 39
3.2 Transition .............................................................................................. 39
3.2.1 Définition et syntaxe ........................................................................ 39
3.2.2 Condition de garde ........................................................................... 39
3.2.3 Effet d'une transition ........................................................................ 40
3.2.4 Transition externe ............................................................................ 40
3.2.5 Transition d'achèvement ................................................................... 40
3.2.6 Transition interne ............................................................................. 40
3.3 Événement ............................................................................................ 41
3.3.1 Notion d'événement ......................................................................... 41
3.3.2 Événement de type signal (signal)...................................................... 42
3.3.3 Événement d'appel (call)................................................................... 42
3.3.4 Événement de changement (change).................................................. 42
3.3.5 Événement temporel (after ou when) ................................................. 43
3.4 Point de choix ........................................................................................ 43
3.4.1 Point de jonction .............................................................................. 43
3.4.2 Point de décision .............................................................................. 44
3.5 États composites .................................................................................... 45
3.5.1 Présentation .................................................................................... 45
3.5.2 Transition........................................................................................ 45
3.5.3 État historique ................................................................................. 46
3.5.4 Interface : les points de connexion ..................................................... 47
3.5.5 Concurrence .................................................................................... 48

36
3.1 Introduction au formalisme

3.1.1 Présentation

Les diagrammes d'états-transitions d'UML décrivent le comportement interne d'un objet à


l'aide d'un automate à états finis. Ils présentent les séquences possibles d'états et
d'actions qu'une instance de classe peut traiter au cours de son cycle de vie en réaction à
des événements discrets (de type signaux, invocations de méthode).
Ils spécifient habituellement le comportement d'une instance d’une classe, mais parfois
aussi le comportement interne d'autres éléments tels que les cas d'utilisation, les sous-
systèmes, les méthodes.

Le diagramme d'états-transitions est le seul diagramme, de la norme UML, à offrir une


vision complète et non ambiguë de l'ensemble des comportements de l'élément auquel il
est attaché. La vision globale du système n'apparaît pas sur ce type de diagrammes
puisqu'ils ne s'intéressent qu'à un seul élément du système indépendamment de son
environnement.

Concrètement, un diagramme d'états-transitions est un graphe qui représente


un automate à états finis, c'est-à-dire une machine dont le comportement des sorties ne
dépend pas seulement de l'état de ses entrées, mais aussi d'un historique des
sollicitations passées.

3.1.2 Notion d'automate à états finis▲

Comme nous venons de le dire, un automate à états finis est un automate dont le
comportement des sorties ne dépend pas seulement de l'état de ses entrées, mais aussi
d'un historique des sollicitations passées. Cet historique est caractérisé par un état
global.

Un état global est un jeu de valeurs d'objet, pour une classe donnée, produisant la même
réponse face aux événements. Toutes les instances d'une même classe ayant le même
état global réagissent de la même manière à un événement. Il ne faut pas confondre les
notions d'état global et d'état. Un automate à états finis est graphiquement représenté
par un graphe comportant des états, matérialisés par des rectangles aux coins arrondis,
et des transitions, matérialisées par des arcs orientés liant les états entre eux.

Figure 3.1 : Un diagramme d'états-transitions simple.

La figure 3.1 montre un exemple simple d'automate à états finis. Cet automate possède
deux états (Allumé et Éteint) et deux transitions correspondant au même événement : la
pression sur un bouton d'éclairage domestique. Cet automate à états finis illustre en fait
le fonctionnement d'un télérupteur dans une maison. Lorsque l'on appuie sur un bouton

37
d'éclairage, la réaction de l'éclairage associé dépendra de son état courant (de son
historique) : si la lumière est allumée, elle s'éteindra, si elle est éteinte, elle s'allumera.
5-1-3. Diagrammes d'états-transitions▲
Un diagramme d'états-transitions rassemble et organise les états et les transitions d'un
classeur donné. Bien entendu, le modèle dynamique du système comprend plusieurs
diagrammes d'états-transitions. Il est souhaitable de construire un diagramme d'états-
transitions pour chaque classeur (qui, le plus souvent, est une classe) possédant un
comportement dynamique important. Un diagramme d'états-transitions ne peut être
associé qu'à un seul classeur. Tous les automates à états finis des diagrammes d'états-
transitions d'un système s'exécutent concurremment et peuvent donc changer d'état de
façon indépendante.
5-2. État▲

3.1.3 Les deux acceptions du terme état

État dans un diagramme d'états-transitions

Figure 3.2 : Exemple d'état simple.

Comme nous l'avons déjà dit, un état, que l'on peut qualifier informellement
d'élémentaire, se représente graphiquement dans un diagramme d'états-transitions par
un rectangle aux coins arrondis (figure 3.2).

Le nom de l'état peut être spécifié dans le rectangle et doit être unique dans le
diagramme d'états-transitions, ou dans l'état enveloppant. On peut l'omettre, ce qui
produit un état anonyme. Il peut y avoir un nombre quelconque d'états anonymes
distincts. Un état imbriqué peut être identifié par son nom qualifié si tous les états
enveloppants ont des noms.

Un état peut être partitionné en plusieurs compartiments séparés par une ligne
horizontale. Le premier compartiment contient le nom de l'état et les autres peuvent
recevoir des transitions internes (cf. section 3.4.6), ou des sous-états (cf. section 3.6),
quand il s'agit d'un état composite. Dans le cas d'un état simple (i.e. sans transition
interne ou sous-état), on peut omettre toute barre de séparation (figure 5.2).

État d'un objet, ou du diagramme d'états-transitions

Un objet peut passer par une série d'états pendant sa durée de vie. Un état représente
une période dans la vie d'un objet pendant laquelle ce dernier attend un événement ou
accomplit une activité. La configuration de l'état global de l'objet est le jeu des états
(élémentaires) qui sont actifs à un instant donné.
Dans le cas d'un diagramme d'états-transitions simple (sans transition concurrente), il ne
peut y avoir qu'un seul état actif à la fois. Dans ce cas, les notions d'état actif et d'état
global se rejoignent.
Cependant, la configuration de l'état global peut contenir plusieurs états actifs à un
instant donné. On parle d'états concurrents (cf. section 3.6.5) quand plusieurs états sont
actifs en même temps et on dit qu'il y a concurrence au sein de l'objet. Le nombre
d'états actifs peut changer pendant la durée de vie d'un objet du fait d'embranchements
ou de jointures appelées transitions concurrentes.

38
3.1.4 État initial et final

État initial

Figure 3.3 : Représentation graphique de l'état initial.

L'état initial est un pseudoétat qui indique l'état de départ, par défaut, lorsque le
diagramme d'états-transitions, ou l'état enveloppant, est invoqué. Lorsqu'un objet est
créé, il entre dans l'état initial.

État final

Figure 3.4 : Représentation graphique de l'état final.

L'état final est un pseudoétat qui indique que le diagramme d'états-transitions, ou l'état
enveloppant, est terminé.

3.2 Transition

3.2.1 Définition et syntaxe

Une transition définit la réponse d'un objet à l'occurrence d'un événement. Elle lie,
généralement, deux états E1 et E2 et indique qu'un objet dans un état E1 peut entrer
dans l'état E2 et exécuter certaines activités, si un événement déclencheur se produit et
que la condition de garde est vérifiée.

La syntaxe d'une transition est la suivante :

événement [ condition ] / <activité>

La syntaxe de <événement> sera définie dans la section 3.3.

Le même événement peut être le déclencheur de plusieurs transitions quittant un même


état. Chaque transition avec le même événement doit avoir une condition de garde
différente (voir figure 3.8). En effet, une seule transition peut se déclencher dans un
même flot d'exécution. Si deux transitions sont activées en même temps par un même
événement, une seule se déclenche et le choix n'est pas prévisible (i.e. pas
déterministe).

3.2.2 Condition de garde

Une transition peut avoir une condition de garde (spécifiée par [condition] dans la
syntaxe). Il s'agit d'une expression logique sur les attributs de l'objet, associé au
diagramme d'états-transitions, ainsi que sur les paramètres de l'événement déclencheur.
La condition de garde est évaluée uniquement lorsque l'événement déclencheur se
produit. Si l'expression est fausse à ce moment-là, la transition ne se déclenche pas, si
elle est vraie, la transition se déclenche et ses effets se produisent (voir figure 3.8).

39
3.2.3 Effet d'une transition

Lorsqu'une transition se déclenche (on parle également de tir d'une transition), son effet
(spécifié par / <activité> dans la syntaxe) s'exécute. Il s'agit généralement d'une activité
qui peut être :
. une opération primitive comme une instruction d'assignation ;
. l'envoi d'un signal ;
. l'appel d'une opération ;
. une liste d'activités, etc.

La façon de spécifier l'activité à réaliser est laissée libre (langage naturel ou


pseudocode).
Lorsque l'exécution de l'effet est terminée, l'état cible de la transition devient actif.

3.2.4 Transition externe

Figure 3.5 : Représentation graphique d'une transition externe entre deux états.

Une transition externe est une transition qui modifie l'état actif. Il s'agit du type de
transition le plus répandu. Elle est représentée par une flèche allant de l'état source vers
l'état cible.
La figure 5.6 illustre la représentation graphique d'une transition externe entre deux
états.

3.2.5 Transition d'achèvement

Une transition dépourvue d'événement déclencheur explicite se déclenche à la fin de


l'activité contenue dans l'état source (y compris les états imbriqués). Elle peut contenir
une condition de garde qui est évaluée au moment où l'activité contenue dans l'état
s'achève, et non pas ensuite.
Les transitions de garde sont, par exemple, utilisées pour connecter les états initiaux et
les états historiques (cf. section 3.5.3) avec leur état successeur puisque ces pseudoétats
ne peuvent rester actifs.

3.2.6 Transition interne

Figure 3.6 : Représentation de la saisie d'un mot de passe dans un état unique en
utilisant des transitions internes.

Les règles de déclenchement d'une transition interne sont les mêmes que pour une
transition externe excepté qu'une transition interne ne possède pas d'état cible et que
l'état actif reste le même à la suite de son déclenchement. La syntaxe d'une transition

40
interne reste la même que celle d'une transition classique. Par contre, les transitions
internes ne sont pas représentées par des arcs, mais sont spécifiées dans un
compartiment de leur état associé (cf. figure 3.6).

Les transitions internes possèdent des noms d'événement prédéfinis correspondant à des
déclencheurs particuliers : entry, exit, do et include. Ces mots-clefs réservés viennent
prendre la place du nom de l'événement dans la syntaxe d'une transition interne.

entry
entry permet de spécifier une activité qui s'accomplit quand on entre dans l'état.
exit
exit permet de spécifier une activité qui s'accomplit quand on sort de l'état.
do
une activité do commence dès que l'activité entry est terminée. Il s’agit d’une
activité qui sera réalisée tant que l’objet sera dans l’état. Lorsque cette activité est
terminée, une transition d'achèvement (de l’état) peut être déclenchée, après l'exécution
de l'activité exit bien entendu. Si une transition (externe à l’état) se déclenche pendant
que l'activité do est en cours, cette dernière est interrompue et l'activité exit de l'état
s'exécute.
include
permet d'invoquer un sous-diagramme d'états-transitions.

Les activités entry servent souvent à effectuer la configuration nécessaire dans un état.
Comme il n'est pas possible de l'éluder, toute action interne à l'état peut supposer que la
configuration est effectuée indépendamment de la manière dont on entre dans l'état. De
manière analogue, une activité exit est une occasion de procéder à un nettoyage. Cela
peut s'avérer particulièrement utile lorsqu'il existe des transitions de haut niveau qui
représentent des conditions d'erreur qui abandonnent les états imbriqués.
Le déclenchement d'une transition interne ne modifie pas l'état actif et n'entraîne donc
pas l'activation des activités entry et exit.

3.3 Événement

3.3.1 Notion d'événement

Un événement est quelque chose qui se produit pendant l'exécution d'un système et qui
mérite d'être modélisé. Les diagrammes d'états-transitions permettent justement de
spécifier les réactions d'une partie du système à des événements discrets. Un événement
se produit à un instant précis et est dépourvu de durée. Quand un événement est reçu,
une transition peut être déclenchée et faire basculer l'objet dans un nouvel état. On peut
diviser les événements en plusieurs types explicites et implicites : signal, appel,
changement et temporel.

41
3.3.2 Événement de type signal (signal)

Figure 3.7 : Déclaration de signaux et héritage.

Un signal est un type de classeur destiné explicitement à véhiculer une communication


asynchrone à sens unique entre deux objets. L'objet expéditeur crée et initialise
explicitement une instance de signal et l'envoi à un objet explicite ou à tout un groupe
d'objets. Il n'attend pas que le destinataire traite le signal pour poursuivre son
déroulement (par exemple, l’envoi d’un email à un consommateur, ou bien l’envoi de la
position de la souris à un objet). La réception d'un signal est un événement pour l'objet
destinataire. Un même objet peut être à la fois expéditeur et destinataire.

Les signaux sont déclarés (dans le diagramme de classes) par la définition d'un classeur
portant le stéréotype « signal » ne fournissant pas d'opération et dont les attributs sont
interprétés comme des arguments (cf. figure 3.7).

La syntaxe d'un signal est la suivante :

<nom_événement> (<paramètre> : <type> ; <paramètre> : <type> ...)

Les signaux supportent la relation de généralisation, héritent des attributs de leurs


parents (héritage) et déclenchent des transitions contenant le type du signal parent
(polymorphisme).

3.3.3 Événement d'appel (call)

Un événement d'appel représente la réception de l'appel d'une opération par un objet.


Les paramètres de l'opération sont ceux de l'événement d'appel. La syntaxe d'un
événement d'appel est la même que celle d'un signal. Par contre, les événements d'appel
sont des méthodes déclarées au niveau du diagramme de classes.

3.3.4 Événement de changement (change)

Un événement de changement est généré par la satisfaction (i.e. passage de faux à vrai)
d'une expression booléenne sur des valeurs d'attributs. Il s'agit d'une manière
déclarative d'attendre qu'une condition soit satisfaite. La syntaxe d'un événement de
changement est la suivante :

when ( <condition_booléenne> )

Notez la différence entre une condition de garde (cf. section 3.2.2) et un événement de
changement. La première est évaluée une fois que l'événement déclencheur de la

42
transition a lieu et que le destinataire le traite. Si elle est fausse, la transition ne se
déclenche pas et la condition n'est pas réévaluée. Un événement de changement est
évalué continuellement jusqu'à ce qu'il devienne vrai, et c'est à ce moment-là que la
transition se déclenche.

3.3.5 Événement temporel (after ou when)

Les événements temporels sont générés par le passage du temps. Ils sont spécifiés soit
de manière absolue (date précise), soit de manière relative (temps écoulé). Par défaut, le
temps commence à s'écouler dès l'entrée dans l'état courant.
La syntaxe d'un événement temporel spécifié de manière relative est la suivante :

after ( <durée> )

when ( date = <date> )

3.4 Point de choix


Il est possible de représenter des alternatives pour le franchissement d'une transition. On
utilise pour cela des pseudoétats particuliers : les points de jonction (représentés par un
petit cercle plein) et les points de décision (représentés par un losange).

3.4.1 Point de jonction

Figure 3.8 : En haut, un diagramme sans point de jonction. En bas, son équivalent
utilisant un point de jonction.

43
Figure 3.9 : Exemple d'utilisation de deux points de jonction pour représenter une
alternative.

Les points de jonction sont un artefact graphique (un pseudoétat en l'occurrence) qui
permet de partager des segments de transition, l'objectif étant d'aboutir à une notation
plus compacte ou plus lisible des chemins alternatifs.
Un point de jonction peut avoir plusieurs segments de transition entrante et plusieurs
segments de transition sortante. Par contre, il ne peut avoir d'activité interne ni des
transitions sortantes dotées de déclencheurs d'événements.

Il ne s'agit pas d'un état qui peut être actif au cours d'un laps de temps fini. Lorsqu'un
chemin passant par un point de jonction est emprunté (donc lorsque la transition
associée est déclenchée) toutes les gardes le long de ce chemin doivent s'évaluer à vrai
dès le franchissement du premier segment.
La figure 3.8 illustre bien l'utilité des points de jonction.
La figure 3.9 illustre l'utilisation de points de jonction pour représenter le branchement
d'une clause conditionnelle.

3.4.2 Point de décision

Figure 3.10 : Exemple d'utilisation d'un point de décision.

Un point de décision possède une entrée et au moins deux sorties. Contrairement à un


point de jonction, les gardes situées après le point de décision sont évaluées au moment
où il est atteint. Cela permet de baser le choix sur des résultats obtenus en franchissant
le segment avant le point de choix (cf. figure 3.10). Si, quand le point de décision est
atteint, aucun segment en aval n'est franchissable, c'est que le modèle est mal formé.

Il est possible d'utiliser une garde particulière, notée [else], sur un des segments en aval
d'un point de choix. Ce segment n'est franchissable que si les gardes des autres
segments sont toutes fausses. L'utilisation d'une clause [else] est recommandée après un
point de décision, car elle garantit un modèle bien formé.

44
3.5 États composites

3.5.1 Présentation

Figure 3.11 : Exemple d'état composite modélisant l'association d'une commande à un


client.

Un état simple ne possède pas de sous-structure, mais uniquement, le cas échéant, un


jeu de transitions internes. Un état composite est un état décomposé en régions
contenant chacune un ou plusieurs sous-états.
Quand un état composite comporte plus d'une région, il est qualifié d'état orthogonal.
Lorsqu'un état orthogonal est actif, un sous-état direct de chaque région est
simultanément actif, il y a donc concurrence. Un état composite ne comportant qu'une
région est qualifié d'état non orthogonal.
Implicitement, tout diagramme d'états-transitions est contenu dans un état externe qui
n'est usuellement pas représenté. Cela apporte une plus grande homogénéité dans la
description : tout diagramme d'états-transitions est implicitement un état composite.

Figure 3.12 : Notation abrégée d'un état composite.

L'utilisation d'états composites permet de développer une spécification par raffinements.


Il n'est pas nécessaire de représenter les sous-états à chaque utilisation de l'état
englobant. Une notation abrégée (figure 3.12) permet d'indiquer qu'un état est
composite et que sa définition est donnée sur un autre diagramme.

3.5.2 Transition

Les transitions peuvent avoir pour cible la frontière d'un état composite et sont
équivalentes à une transition ayant pour cible l'état initial de l'état composite.
Une transition ayant pour source la frontière d'un état composite est équivalente à une
transition qui s'applique à tout sous-état de l'état composite source. Cette relation est
transitive : la transition est franchissable depuis tout état imbriqué, quelle que soit sa
profondeur.
Par contre, si la transition ayant pour source la frontière d'un état composite ne porte pas
de déclencheur explicite (i.e. s'il s'agit d'une transition d'achèvement), elle est
franchissable quand l'état final de l'état composite est atteint.
Les transitions peuvent également toucher des états de différents niveaux d'imbrication
en traversant les frontières des états composites.

45
Figure 3.13 : Exemple de configuration complexe de transition. Depuis l'état État 1, la
réception de l'événement event1 produit la séquence d'activités QuitterE11, QuitterE1,
action1, EntrerE2, EntrerE21, initialiser(), EntrerE22, et place le système dans l'état
État22.

La figure 3.13 illustre une configuration complexe de transition produisant une cascade
d'activités.

3.5.3 État historique

Figure 3.14 : Exemple de diagramme possédant un état historique profond permettant de


reprendre le programme de lavage ou de séchage d'une voiture à l'endroit où il était
arrivé avant d'être interrompu.

Un état historique, également qualifié d'état historique plat, est un pseudoétat qui
mémorise le dernier sous-état actif d'un état composite. Graphiquement, il est
représenté par un cercle contenant un H.
Une transition ayant pour cible l'état historique est équivalente à une transition qui a
pour cible le dernier état visité de l'état englobant. Un état historique peut avoir une
transition sortante non étiquetée indiquant l'état à exécuter si la région n'a pas encore
été visitée.
Il est également possible de définir un état historique profond représenté graphiquement
par un cercle contenant un H*. Cet état historique profond permet d'atteindre le dernier
état visité dans la région, quel que soit son niveau d'imbrication, alors que le l'état
historique plat limite l'accès aux états de son niveau d'imbrication.

46
La figure 3.14 montre un diagramme d'états-transitions modélisant le lavage
automatique d'une voiture. Les états de lavage, séchage et lustrage sont des états
composites définis sur trois autres diagrammes d'états-transitions non représentés ici. En
phase de lavage ou de séchage, le client peut appuyer sur le bouton d'arrêt d'urgence.
S'il appuie sur ce bouton, la machine se met en attente. Il a alors deux minutes pour
reprendre le lavage ou le lustrage, exactement où le programme a été interrompu, c'est-
à-dire au niveau du dernier sous-état actif des états de lavage ou de lustrage (état
historique profond). Si l'état n’avait pas été un état historique plat, c'est toute la
séquence de lavage ou de lustrage qui aurait recommencé. En phase de lustrage, le
client peut aussi interrompre la machine. Mais dans ce cas, la machine s'arrêtera
définitivement.

3.5.4 Interface : les points de connexion

Figure 3.15 : Exemple d'utilisation de points de connexion.

Comme nous l'avons déjà dit, il est possible de masquer les sous-états d'un état
composite et de les définir dans un autre diagramme. Cette pratique nécessite parfois
l'utilisation de pseudoétats appelés points de connexion.
Lorsque l'on utilise le comportement par défaut de l'état composite, c'est-à-dire entrer
par l'état initial par défaut et considérer les traitements finis quand l'état final est atteint,
aucun problème ne se pose, car on utilise des transitions ayant pour cible, ou pour
source, la frontière de l'état composite. Dans ce cas, les points de connexion sont
inutiles.
Le problème se pose lorsqu'il est possible d'entrer ou de sortir d'un état composite de
plusieurs façons. C'est, par exemple, le cas lorsqu'il existe des transitions traversant la
frontière de l'état composite et visant directement, ou ayant pour source, un sous-état de
l'état composite. Dans ce cas, la solution est d'utiliser des points de connexion sur la
frontière de l'état composite.
Les points de connexion sont des points d'entrée et de sortie portant un nom, et situés
sur la frontière d'un état composite. Ils sont respectivement représentés par un cercle
vide et un cercle barré d'une croix (cf. figure 3.15). Il ne s'agit que de références à un
état défini dans l'état composite. Une unique transition d'achèvement, dépourvue de
garde, relie le pseudoétat source (i.e. le point de connexion) à l'état référencé. Cette
transition d'achèvement n'est que le prolongement de la transition qui vise le point de
connexion (il peut d'ailleurs y en avoir plusieurs). Les points de connexions offrent ainsi
une façon de représenter l'interface (au sens objet) d'un état composite en masquant
l'implémentation de son comportement.
On peut considérer que les pseudoétats initiaux et finals sont des points de connexion
sans nom.

47
3.5.5 Concurrence

Figure 3.16 : Exemple d'utilisation d'un état composite orthogonal.

Les diagrammes d'états-transitions permettent de décrire efficacement les mécanismes


concurrents grâce à l'utilisation d'états orthogonaux. Un état orthogonal est un état
composite comportant plus d'une région, chaque région représentant un flot d'exécution.
Graphiquement, dans un état orthogonal, les différentes régions sont séparées par un
trait horizontal en pointillé allant du bord gauche au bord droit de l'état composite.
Chaque région peut posséder un état initial et final. Une transition qui atteint la bordure
d'un état composite orthogonal est équivalente à une transition qui atteint les états
initiaux de toutes ses régions concurrentes.
Toutes les régions concurrentes d'un état composite orthogonal doivent atteindre leur
état final pour que l'état composite soit considéré comme terminé.
La figure 3.16 illustre l'utilisation d'un état composite orthogonal pour modéliser le fait
que la préparation de la boisson d'un distributeur de boissons se fait en parallèle au
rendu de la monnaie.

Figure 3.17 : Exemple d'utilisation de transitions complexes.

Il est également possible de représenter ce type de comportement au moyen de


transitions concurrentes. De telles transitions sont qualifiées de complexes. Les
transitions complexes sont représentées par une barre épaisse et peuvent,
éventuellement, être nommées. La figure 3.17 montre la mise en œuvre de ce type de
transition. Sur ce diagramme, l'état orthogonal préparer boisson et rendre monnaie peut
éventuellement ne pas apparaître (tout en gardant la représentation de ses sous-états)
pour alléger la représentation, car la notion de concurrence est clairement apparente de
par l'utilisation des transitions complexes.

48
4 Diagramme d'activités (Activity diagram)
4 Diagramme d'activités (Activity diagram) ......................................................... 49
4.1 Introduction au formalisme ...................................................................... 50
4.1.1 Présentation .................................................................................... 50
4.1.2 Utilisation courante .......................................................................... 50
4.2 Activité et Transition ............................................................................... 50
4.2.1 Action (action) ................................................................................. 50
4.2.2 Activité (activity) ............................................................................. 51
4.2.3 Groupe d'activités ............................................................................ 51
4.2.4 Nœud d'activité................................................................................ 51
4.2.5 Transition........................................................................................ 52
4.3 Nœud exécutable .................................................................................... 52
4.3.1 Nœud d'action ................................................................................. 52
4.3.2 Nœud d'activité structurée ................................................................ 52
4.4 Nœud de contrôle ................................................................................... 53
4.4.1 Nœud initial..................................................................................... 54
4.4.2 Nœud final ...................................................................................... 54
Nœud de fin d'activité................................................................................. 54
Nœud de fin de flot .................................................................................... 54
4.4.3 Nœud de décision et de fusion ........................................................... 54
Nœud de décision....................................................................................... 54
Nœud de fusion ......................................................................................... 55
4.4.4 Nœud de bifurcation et d'union .......................................................... 55
Nœud de bifurcation ou de débranchement ................................................... 55
Nœud d'union ou de jointure ....................................................................... 55
4.5 Nœud d'objet ......................................................................................... 55
4.5.1 Introduction .................................................................................... 55
4.5.2 Pin d'entrée ou de sortie ................................................................... 56
4.5.3 Pin de valeur ................................................................................... 56
4.5.4 Flot d'objets .................................................................................... 56
4.5.5 Nœud tampon central ....................................................................... 57
4.5.6 Nœud de stockage des données ......................................................... 57
4.6 Partitions ............................................................................................... 58
4.7 Exceptions ............................................................................................. 59

49
4.1 Introduction au formalisme

4.1.1 Présentation

Les diagrammes d'activités permettent de mettre l'accent sur les traitements. Ils sont
donc particulièrement adaptés à la modélisation du cheminement de flots de contrôle et
de flots de données. Ils permettent ainsi de représenter graphiquement le comportement
d'une méthode ou le déroulement d'un cas d'utilisation.

Les diagrammes d'activités sont relativement proches des diagrammes d'états-transitions


dans leur présentation, mais leur interprétation est sensiblement différente. Les
diagrammes d'états-transitions sont orientés vers des systèmes réactifs, mais ils ne
donnent pas une vision satisfaisante d'un traitement faisant intervenir plusieurs classeurs
et doivent être complétés, par exemple, par des diagrammes de séquence. Au contraire,
les diagrammes d'activités ne sont pas spécifiquement rattachés à un classeur particulier.
On peut attacher un diagramme d'activités à n'importe quel élément de modélisation afin
de visualiser, spécifier, construire ou documenter le comportement de cet élément.
La différence principale entre les diagrammes d'interaction et les diagrammes d'activités
est que les premiers mettent l'accent sur le flot de contrôle d'un objet à l'autre, tandis
que les seconds insistent sur le flot de contrôle d'une activité à l'autre.

4.1.2 Utilisation courante

Dans la phase de conception, les diagrammes d'activités sont particulièrement adaptés à


la description des cas d'utilisation. Plus précisément, ils viennent illustrer et consolider la
description textuelle des cas d'utilisation. De plus, leur représentation sous forme
d'organigrammes les rend facilement intelligibles et beaucoup plus accessibles que les
diagrammes d'états-transitions. On parle généralement dans ce cas de modélisation
de workflow. On se concentre ici sur les activités telles que les voient les acteurs qui
collaborent avec le système dans le cadre d'un processus métier. La modélisation du flot
d'objets est souvent importante dans ce type d'utilisation des diagrammes d'activités.
Les diagrammes d'activités permettent de spécifier des traitements a priori séquentiels et
offrent une vision très proche de celle des langages de programmation impératifs comme
C++ ou Java. Ainsi, ils peuvent être utiles dans la phase de réalisation, car ils permettent
une description si précise des opérations qu'elle autorise la génération automatique du
code. La modélisation d'une opération peut toutefois être assimilée à une utilisation
d'UML comme langage de programmation visuelle, ce qui n'est pas sa finalité. Il ne faut
donc pas y avoir recours de manière systématique, mais la réserver à des opérations
dont le comportement est complexe ou sensible.

4.2 Activité et Transition

4.2.1 Action (action)

Une action est le plus petit traitement qui puisse être exprimé en UML. Une action a une
incidence sur l'état du système ou en extrait une information. Les actions sont des étapes
discrètes à partir desquelles se construisent les comportements. La notion d'action est à
rapprocher de la notion d'instruction élémentaire d'un langage de programmation
(comme C++ ou Java). Une action peut être une affectation de valeur à des attributs, un

50
accès à la valeur d'une propriété structurelle (attribut ou terminaison d'association), la
création d'un nouvel objet ou lien, un calcul arithmétique simple, l'émission d'un signal,
la réception d'un signal, etc.

4.2.2 Activité (activity)

Une activité définit un comportement décrit par un séquencement organisé d'unités dont
les éléments simples sont les actions. Le flot d'exécution est modélisé par des nœuds
reliés par des arcs (transitions). Le flot de contrôle reste dans l'activité jusqu'à ce que les
traitements soient terminés.
Une activité est un comportement (behavior en anglais) et à ce titre peut être associée à
des paramètres.

4.2.3 Groupe d'activités

Un groupe d’activité est une activité regroupant des nœuds et des arcs. Les nœuds et les
arcs peuvent appartenir à plus d'un groupe. Un diagramme d'activités est lui-même un
groupe d'activités (cf. figure 4.2).

4.2.4 Nœud d'activité

Figure 4.1 : Représentation graphique des nœuds d'activité. De la gauche vers la droite,
on trouve : le nœud représentant une action, qui est une variété de nœud exécutable, un
nœud objet, un nœud de décision ou de fusion, un nœud de bifurcation ou d'union, un
nœud initial, un nœud final et un nœud final de flot.

Figure 4.2 : Exemple de diagramme d'activités modélisant le fonctionnement d'une borne


bancaire.

51
Un nœud d'activité est un type d'élément abstrait permettant de représenter les étapes
le long du flot d'une activité. Il existe trois familles de nœuds d'activités : les nœuds
d'exécution, les nœuds objets et les nœuds de contrôle.

La figure 4.1 représente les différents types de nœuds d'activité. La figure 4.2 montre
comment certains de ces nœuds sont utilisés pour former un diagramme d'activités.

4.2.5 Transition

Figure 4.3 : Représentation graphique d'une transition.

Le passage d'une activité vers une autre est matérialisé par une transition.
Graphiquement les transitions sont représentées par des flèches en traits pleins qui
connectent les activités entre elles (figure 4.3). Elles sont déclenchées dès que l'activité
source est terminée et provoquent automatiquement et immédiatement le début de la
prochaine activité à déclencher (l'activité cible). Contrairement aux activités, les
transitions sont franchies de manière atomique, en principe sans durée perceptible.
Les transitions spécifient l'enchaînement des traitements et définissent le flot de contrôle.

4.3 Nœud exécutable


Un nœud exécutable est un nœud d'activité qu'on peut exécuter (i.e. une activité). Il
possède un gestionnaire d'exceptions qui peut capturer les exceptions levées par le
nœud, ou un de ses nœuds imbriqués.

4.3.1 Nœud d'action

Figure 4.4 : Représentation graphique d'un nœud d'action.

Un nœud d'action est un nœud d'activité exécutable qui constitue l'unité fondamentale de
fonctionnalité exécutable dans une activité. L'exécution d'une action représente une
transformation ou un calcul quelconque dans le système modélisé. Les actions sont
généralement liées à des opérations qui sont directement invoquées. Un nœud d'action
doit avoir au moins un arc entrant.
Graphiquement, un nœud d'action est représenté par un rectangle aux coins arrondis
(figure 4.4) qui contient sa description textuelle. Cette description textuelle peut aller
d'un simple nom à une suite d'actions réalisées par l'activité. UML n'impose aucune
syntaxe pour cette description textuelle, on peut donc utiliser une syntaxe proche de
celle d'un langage de programmation particulier ou du pseudocode.

4.3.2 Nœud d'activité structurée

Un nœud d'activité structurée est un nœud d'activité exécutable qui représente une
portion structurée d'une activité donnée qui n'est partagée avec aucun autre nœud

52
structuré, à l'exception d'une imbrication éventuelle. On peut voir une nœud d’activité
structurée comme un sous nœud, ou un sous diagramme d’activité, permettant de
décrire un comportement, au même titre que les états composites pour les diagrammes
d’états-transitions.

Les transitions d'une activité structurée doivent avoir leurs nœuds source et cible dans le
même nœud d'activité structurée. Les nœuds et les arcs contenus par nœud d'activité
structuré ne peuvent pas être contenus dans un autre nœud d'activité structuré
(autrement dit, quand on est dans un sous diagramme d’activité, on reste dedans jusqu’à
la fin).

Un nœud structuré est dénoté par le stéréotype « structured » et identifié par un nom
unique décrivant le comportement modélisé dans l'activité structurée.
Graphiquement, le contour d'un nœud d'activité structurée est en pointillé. Une ligne
horizontale en trait continu sépare le compartiment contenant le
stéréotype « structured » et le nom de l'activité structurée du corps de l'activité
structurée.

4.4 Nœud de contrôle

Figure 4.5 : Exemple de diagramme d'activité illustrant l'utilisation de nœuds de contrôle.


Ce diagramme décrit la prise en compte d'une commande.

Un nœud de contrôle est un nœud d'activité abstrait utilisé pour coordonner les flots
entre les nœuds d'une activité.
Il existe plusieurs types de nœuds de contrôle :
• nœud initial
• nœud de fin d'activité
• nœud de fin de flot
• nœud de décision
• nœud de fusion
• nœud de bifurcation
• nœud d'union
La figure 4.5 illustre l'utilisation de ces nœuds de contrôle.

53
4.4.1 Nœud initial

Un nœud initial est un nœud de contrôle à partir duquel le flot débute lorsque l'activité
enveloppante est invoquée. Une activité peut avoir plusieurs nœuds initiaux. Un nœud
initial possède un arc sortant et pas d'arc entrant.
Graphiquement, un nœud initial est représenté par un petit cercle plein (cf. figure 4.5).

4.4.2 Nœud final

Un nœud final est un nœud de contrôle possédant un ou plusieurs arcs entrants et aucun
arc sortant.

Nœud de fin d'activité

Lorsque l'un des arcs d'un nœud de fin d'activité est activé (i.e. lorsqu'un flot d'exécution
atteint un nœud de fin d'activité), l'exécution de l'activité enveloppante s'achève et tout
nœud ou flot actif au sein de l'activité enveloppante est abandonné. Si l'activité a été
invoquée par un appel synchrone, un message (reply) contenant les valeurs sortantes est
transmis en retour à l'appelant.
Graphiquement, un nœud de fin d'activité est représenté par un cercle vide contenant un
petit cercle plein (cf. figure 4.5).

Nœud de fin de flot

Lorsqu'un flot d'exécution atteint un nœud de fin de flot, le flot en question est terminé,
mais cette fin de flot n'a aucune incidence sur les autres flots actifs de l'activité
enveloppante.
Graphiquement, un nœud de fin de flot est représenté par un cercle vide barré d'un X.
Les nœuds de fin de flot sont particuliers et à utiliser avec parcimonie. Dans l'exemple de
la figure 4.5, le nœud de fin de flot n'est pas indispensable : on peut le remplacer par un
nœud d'union possédant une transition vers un nœud de fin d'activité.

4.4.3 Nœud de décision et de fusion

Nœud de décision

Un nœud de décision est un nœud de contrôle qui permet de faire un choix entre
plusieurs flots sortants. Il possède un arc entrant et plusieurs arcs sortants. Ces derniers
sont généralement accompagnés de conditions de garde pour conditionner le choix. Si,
quand le nœud de décision est atteint, aucun arc en aval n'est franchissable (i.e. aucune
condition de garde n'est vraie), c'est que le modèle est mal formé.
L'utilisation d'une garde [else] est recommandée après un nœud de décision, car elle
garantit un modèle bien formé. En effet, la condition de garde [else] est validée si et
seulement si toutes les autres gardes des transitions ayant la même source sont fausses.
Dans le cas où plusieurs arcs sont franchissables (i.e. plusieurs conditions de garde sont
vraies), seul l'un d'entre eux est retenu et ce choix est non déterministe.

Graphiquement, on représente un nœud de décision par un losange (cf. figure 4.5).

54
Nœud de fusion

Un nœud de fusion est un nœud de contrôle qui rassemble plusieurs flots alternatifs
entrants en un seul flot sortant. Il n'est pas utilisé pour synchroniser des flots
concurrents (c'est le rôle du nœud d'union), mais pour accepter un flot parmi plusieurs.
Graphiquement, on représente un nœud de fusion, comme un nœud de décision, par un
losange (cf. figure 4.5).
Graphiquement, il est possible de fusionner un nœud de fusion et un nœud de décision,
et donc d'avoir un losange possédant plusieurs arcs entrants et sortants. Il est également
possible de fusionner un nœud de décision ou de fusion avec un autre nœud, comme un
nœud de fin de flot sur la figure 4.5, ou avec une activité. Cependant, pour mieux mettre
en évidence un branchement conditionnel, il est préférable d'utiliser un nœud de décision
(losange).

4.4.4 Nœud de bifurcation et d'union

Nœud de bifurcation ou de débranchement

Un nœud de bifurcation, également appelé nœud de débranchement est un nœud de


contrôle qui sépare un flot en plusieurs flots concurrents. Un tel nœud possède donc un
arc entrant et plusieurs arcs sortants. On apparie généralement un nœud de bifurcation
avec un nœud d'union pour équilibrer la concurrence (cf. figure 4.5).
Graphiquement, on représente un nœud de bifurcation par un trait plein (cf. figure 4.5).

Nœud d'union ou de jointure

Un nœud d'union, également appelé nœud de jointure est un nœud de contrôle qui
synchronise des flots multiples. Un tel nœud possède donc plusieurs arcs entrants et un
seul arc sortant. Lorsque tous les arcs entrants sont activés, l'arc sortant l'est également.
Graphiquement, on représente un nœud d'union, comme un nœud de bifurcation, par un
trait plein (cf. figure 4.5).
Graphiquement, il est possible de fusionner un nœud de bifurcation et un nœud d'union,
et donc d'avoir un trait plein possédant plusieurs arcs entrants et sortants (cf. figure
4.5).

4.5 Nœud d'objet

4.5.1 Introduction

Jusqu'ici, nous avons montré comment modéliser le comportement du flot de contrôle


dans un diagramme d'activités. Or, les flots de données n'apparaissent pas et sont
pourtant un élément essentiel des traitements (arguments des opérations, valeurs de
retour…).
Un nœud d'objet permet de définir un flot d'objets (i.e. un flot de données) dans un
diagramme d'activités. Ce nœud représente l'existence d'un objet généré par une action
dans une activité et utilisé par d'autres actions.

55
4.5.2 Pin d'entrée ou de sortie

Figure 4.6 : Représentation des pins d'entrée et de sortie sur une activité.

Pour spécifier les valeurs passées en argument à une activité et les valeurs de retour, on
utilise des nœuds d'objets appelés pins (pin en anglais) d'entrée ou de sortie. L'activité
ne peut débuter que si l'on affecte une valeur à chacun de ses pins d'entrée. Quand
l'activité se termine, une valeur doit être affectée à chacun de ses pins de sortie.
Les valeurs sont passées par copie : une modification des valeurs d'entrée au cours du
traitement de l'action n'est visible qu'à l'intérieur de l'activité.

Graphiquement, un pin est représenté par un petit carré attaché à la bordure d'une
activité (cf. figure 6.7). Il est typé et éventuellement nommé. Il peut contenir des flèches
indiquant sa direction (entrée ou sortie) si l'activité ne permet pas de le déterminer de
manière univoque.

4.5.3 Pin de valeur

Un pin valeur est un pin d'entrée qui fournit une valeur à une action sans que cette
valeur ne provienne d'un arc de flot d'objets. Un pin valeur est toujours associé à une
valeur spécifique.
Graphiquement, un pin de valeur se représente comme un pin d'entrée avec la valeur
associée écrite à proximité.

4.5.4 Flot d'objets

Figure 4.7 : Deux notations possibles pour modéliser un flot de données.

Un flot d'objets permet de passer des données d'une activité à une autre. Un arc reliant
un pin de sortie à un pin d'entrée est, par définition même des pins, un flot d'objets (en
haut de la figure 4.7). Dans cette configuration, le type du pin récepteur doit être
identique ou parent (au sens de la relation de généralisation) du type du pin émetteur.

Il existe une autre représentation possible d'un flot d'objets, plus axée sur les données
proprement dites, car elle fait intervenir un nœud d'objet détaché d'une activité
particulière (en bas de la figure 4.7). Graphiquement, un tel nœud d'objet est représenté
par un rectangle dans lequel est mentionné le type de l'objet (souligné). Des arcs
viennent ensuite relier ce nœud d'objet à des activités sources et cibles. Le nom d'un
état, ou d'une liste d'états, de l'objet peut être précisé entre crochets après ou sous le

56
type de l'objet. On peut également préciser des contraintes entre accolades, soit à
l'intérieur, soit en dessous du rectangle du nœud d'objet.
La figure 4.10 montre l'utilisation de nœuds d'objets dans un diagramme d'activités.
Un flot d'objets peut porter une étiquette stéréotypée mentionnant deux comportements
particuliers : « transformation » indique une interprétation particulière de la donnée
véhiculée par le flot, et « selection » indique l'ordre dans lequel les objets sont choisis
dans le nœud pour le quitter (cf. figure 4.9).

4.5.5 Nœud tampon central

Figure 4.8 : Exemple d'utilisation d'un nœud tampon central pour centraliser toutes les
commandes prises par différents procédés, avant qu'elles soient traitées.
Un nœud tampon central est un nœud d'objet qui accepte les entrées de plusieurs nœuds
d'objets ou produit des sorties vers plusieurs nœuds d'objets. Les flots en provenance
d'un nœud tampon central ne sont donc pas directement connectés à des actions. Ce
nœud modélise donc un tampon traditionnel qui peut contenir des valeurs en provenance
de diverses sources et livrer des valeurs vers différentes destinations.
Graphiquement, un nœud tampon central est représenté comme un nœud d'objet
détaché (en bas de la figure 4.7) stéréotypé « centralBuffer » (cf. figure 4.8).

4.5.6 Nœud de stockage des données

Figure 4.9 : Dans cette modélisation, le personnel, après avoir été recruté par l'activité
Recruter personnel, est stocké de manière persistante dans le nœud de stockage Base de
données du Personnel. Bien qu'ils restent dans ce nœud, chaque employé qui n'a pas
encore reçu d'affectation (étiquette stéréotypée « selection » :
[Link]=null) est disponible pour être utilisé par l'activité Affecter
personnel.

Un nœud de stockage des données est un nœud tampon central particulier qui assure la
persistance des données. Lorsqu'une information est sélectionnée par un flux sortant,
l'information est dupliquée et ne disparaît pas du nœud de stockage des données comme
ce serait le cas dans un nœud tampon central. Lorsqu'un flux entrant véhicule une
donnée déjà stockée par le nœud de stockage des données, cette dernière est écrasée
par la nouvelle.

57
Graphiquement, un nœud tampon central est représenté comme un nœud d'objet
détaché (en bas de la figure 4.7) stéréotypé « data store » (cf. figure 4.9).

4.6 Partitions

Figure 4.10 : Illustration de l'utilisation de nœuds d'objets et de partitions dans un


diagramme d'activités.

Les partitions, souvent appelées couloirs ou lignes d'eau du fait de leur notation,
permettent d'organiser les nœuds d'activités dans un diagramme d'activités en opérant
des regroupements (cf. figure 4.10).
Les partitions n'ont pas de signification bien arrêtée, mais correspondent souvent à des
unités d'organisation du modèle. On peut, par exemple, les utiliser pour spécifier la
classe responsable de la mise en œuvre d'un ensemble de tâches. Dans ce cas, la classe
en question est responsable de l'implémentation du comportement des nœuds inclus
dans ladite partition.
Graphiquement, les partitions sont délimitées par des lignes continues. Il s'agit
généralement de lignes verticales, comme sur la figure 4.10, mais elles peuvent être
horizontales ou même courbes. Dans le cas d'un diagramme d'activités partitionné, les
nœuds d'activités appartiennent forcément à une et une seule partition. Les transitions
peuvent, bien entendu, traverser les frontières des partitions.

Les partitions d'activités étant des catégories arbitraires, on peut les représenter par
d'autres moyens quand une répartition géométrique s'avère difficile à réaliser. On peut
ainsi utiliser des couleurs ou tout simplement étiqueter les nœuds d'activité par le nom
de leur partition d'appartenance.

58
4.7 Exceptions

Figure 4.11 : Notation graphique du fait qu'une activité peut soulever une exception.

Une exception est générée quand une situation anormale entrave le déroulement nominal
d'une tâche. Elle peut être générée automatiquement pour signaler une erreur
d'exécution (débordement d'indice de tableau, division par zéro…), ou être soulevée
explicitement par une action (RaiseException) pour signaler une situation problématique
qui n'est pas prise en charge par la séquence de traitement normale.
Graphiquement, on peut représenter le fait qu'une activité peut soulever une exception
comme un pin de sortie orné d'un petit triangle et en précisant le type de l'exception à
proximité du pin de sortie (cf. figure 4.11).

Figure 4.12 : Les deux notations graphiques de la connexion entre une activité protégée
et son gestionnaire d'exceptions associé.

Un gestionnaire d'exceptions est une activité possédant un pin d'entrée du type de


l'exception qu'il gère et lié à l'activité qu'il protège par un arc en zigzag ou un arc
classique orné d'une petite flèche en zigzag. Le gestionnaire d'exceptions doit avoir les
mêmes pins de sortie que le bloc qu'il protège (cf. figure 4.12).

Les exceptions sont des classeurs et, à ce titre, peuvent posséder des caractéristiques
comme des attributs ou des opérations. Il est également possible d'utiliser la relation
d'héritage sur les exceptions. Un gestionnaire d'exceptions spécifie toujours le type des
exceptions qu'il peut traiter, toute exception dérivant de ce type est donc également
prise en charge.

59
5 Diagramme de séquence
5.1 Représentation des lignes de vie............................................................... 60
5.2 Représentation des messages .................................................................. 60
5.2.1 Messages asynchrones...................................................................... 61
5.2.2 Messages synchrones ....................................................................... 61
5.2.3 Messages de création et destruction d'instance .................................... 61
5.2.4 Événements et messages .................................................................. 62
5.2.5 Syntaxe des messages et des réponses .............................................. 62
5.2.6 Message perdu et trouvé ................................................................... 63
5.2.7 Porte .............................................................................................. 63
5.2.8 Exécution de méthode et objet actif ................................................... 63
5.3 Fragments d'interaction combinés ............................................................. 64
5.3.1 Introduction .................................................................................... 64
5.3.2 Opérateur alt ................................................................................... 65
5.3.3 Opérateurs opt ................................................................................ 65
5.3.4 Opérateur loop ................................................................................ 65
5.3.5 Opérateur par .................................................................................. 66
5.3.6 Opérateur strict ............................................................................... 66
5.4 Utilisation d'interaction ............................................................................ 67

Les principales informations contenues dans un diagramme de séquence sont les


messages échangés entre les lignes de vie, présentés dans un ordre chronologique. Ainsi,
contrairement à la plupart des diagrammes, le temps y est représenté explicitement par
une dimension (la dimension verticale) et s'écoule de haut en bas (cf. figure XXX).

5.1 Représentation des lignes de vie


Une ligne de vie se représente par un rectangle, auquel est accroché une ligne verticale
pointillée, contenant une étiquette dont la syntaxe est :

[<nom_du_rôle>] : [<Nom_du_type>]

Au moins un des deux noms doit être spécifié dans l'étiquette, les deux points (:) sont,
quant à eux, obligatoires.

5.2 Représentation des messages


Un message définit une communication particulière entre des lignes de vie. Plusieurs
types de messages existent, les plus communs sont :
l'envoi d'un signal ;
l'invocation d'une opération ;
la création ou la destruction d'une instance.

60
5.2.1 Messages asynchrones

Figure 5.1 : Représentation d'un message asynchrone.

Une interruption ou un événement sont de bons exemples de signaux. Ils n'attendent pas
de réponse et ne bloquent pas l'émetteur qui ne sait pas si le message arrivera à
destination, et s'il sera traité par le destinataire. Un signal est, par définition, un message
asynchrone.
Graphiquement, un message asynchrone se représente par une flèche en traits pleins et
à l'extrémité ouverte partant de la ligne de vie d'un objet expéditeur et allant vers celle
de l'objet cible (figure 5.1).

5.2.2 Messages synchrones

Figure 5.2 : Représentation d'un message synchrone.

L'invocation d'une opération est le type de message le plus utilisé en programmation


objet. L'invocation peut être asynchrone ou synchrone. Dans la pratique, la plupart des
invocations sont synchrones, l'émetteur reste alors bloqué le temps que dure l'invocation
de l'opération.
Graphiquement, un message synchrone se représente par une flèche en traits pleins et à
l'extrémité pleine partant de la ligne de vie d'un objet expéditeur et allant vers celle de
l'objet cible (figure 5.2). Ce message peut être suivi d'une réponse qui se représente par
une flèche en pointillé (figure 5.2).

5.2.3 Messages de création et destruction d'instance

Figure 5.3 : Représentation d'un message de création et destruction d'instance.

La création d'un objet est matérialisée par une flèche qui pointe sur le sommet d'une
ligne de vie (figure 5.3).

61
La destruction d'un objet est matérialisée par une croix qui marque la fin de la ligne de
vie de l'objet (figure 5.3). La destruction d'un objet n'est pas nécessairement consécutive
à la réception d'un message.

5.2.4 Événements et messages

Figure 5.4 : Les différents évènements correspondant à un message asynchrone.

UML permet de séparer clairement l'envoi du message, sa réception, ainsi que le début
de l'exécution de la réaction et sa fin (figure 5.4).

5.2.5 Syntaxe des messages et des réponses

Figure 5.5 : Syntaxe des messages et des réponses.

Dans la plupart des cas, la réception d'un message est suivie de l'exécution d'une
méthode d'une classe. Cette méthode peut recevoir des arguments et la syntaxe des
messages permet de transmettre ces arguments. La syntaxe de ces messages est la
même que pour un diagramme de communication excepté deux points :

62
la direction du message est directement spécifiée par la direction de la flèche qui
matérialise le message, et non par une flèche supplémentaire au-dessus du connecteur
reliant les objets comme c'est le cas dans un diagramme de communication ;
les numéros de séquence sont généralement omis puisque l'ordre relatif des
messages est déjà matérialisé par l'axe vertical qui représente l'écoulement du temps.
La syntaxe de réponse à un message est la suivante :

[<attribut> = ] message [ : <valeur_de_retour>]


où message représente le message d'envoi.

5.2.6 Message perdu et trouvé

Figure 5.6 : Représentation d'un message perdu et d'un message trouvé.

Un message complet est tel que les événements d'envoi et de réception sont connus.
Comme nous l'avons déjà vu, un message complet se représente par une simple flèche
dirigée de l'émetteur vers le récepteur.
Un message perdu est tel que l'événement d'envoi est connu, mais pas l'événement de
réception. Il se représente par une flèche qui pointe sur une petite boule noire (figure
5.6).
Un message trouvé est tel que l'événement de réception est connu, mais pas
l'événement d'émission. Une flèche partant d'une petite boule noire représente un
message trouvé (figure 5.6).

5.2.7 Porte

Une porte est un point de connexion qui permet de représenter un même message dans
plusieurs fragments d'interaction. Ces messages entrants et sortants vont d'un bord d'un
diagramme à une ligne de vie (ou l'inverse).

5.2.8 Exécution de méthode et objet actif

Figure 5.7 : Représentation d'un objet actif (à gauche) et d'une exécution sur un objet
passif (à droite).

Un objet actif initie et contrôle le flux d'activités. Graphiquement, la ligne pointillée


verticale d'un objet actif est remplacée par un double trait vertical (cf. figures 5 .7, 5.3).

63
Un objet passif, au contraire, a besoin qu'on lui donne le flux d'activité pour pouvoir
exécuter une méthode. La spécification de l'exécution d'une réaction sur un objet passif
se représente par un rectangle blanc ou gris placé sur la ligne de vie en pointillé (cf.
figures 7.13). Le rectangle peut éventuellement porter un label.

Figure 5.8 : Représentation d'une exécution simultanée (à gauche).


Les réactions sur une même ligne de vie sont représentées par un rectangle chevauchant
comme le montre la figure 5.8.

5.3 Fragments d'interaction combinés

5.3.1 Introduction

Un fragment combiné représente des articulations d'interactions. Il est défini par un


opérateur et des opérandes. L'opérateur conditionne la signification du fragment
combiné. Il existe 12 d'opérateurs définis dans la notation UML 2.0. Les fragments
combinés permettent de décrire des diagrammes de séquence de manière compacte. Les
fragments combinés peuvent faire intervenir l'ensemble des entités participant au
scénario ou juste un sous-ensemble.
Un fragment combiné se représente de la même façon qu'une interaction. Il est
représenté dans un rectangle dont le coin supérieur gauche contient un pentagone. Dans
le pentagone figure le type de la combinaison, appelé opérateur d'interaction. Les
opérandes d'un opérateur d'interaction sont séparés par une ligne pointillée. Les
conditions de choix des opérandes sont données par des expressions booléennes entre
crochets ([ ]).
La liste suivante regroupe les opérateurs d'interaction par fonctions :
.les opérateurs de choix et de boucle : alternative, option, break et loop ;
.les opérateurs contrôlant l'envoi en parallèle de messages : parallel et critical region ;
.les opérateurs contrôlant l'envoi de messages : ignore, consider, assertion et negative ;
.les opérateurs fixant l'ordre d'envoi des messages : weak sequencing , strict
sequencing.
Nous n'aborderons que quelques-unes de ces interactions dans la suite de cette section.

64
5.3.2 Opérateur alt

Figure 5.9 : Représentation d'un choix dans un diagramme de séquence illustrant le


découvrement d'une case au jeu du démineur.

L'opérateur alternative, ou alt, est un opérateur conditionnel possédant plusieurs


opérandes (cf. figure 5.9). C'est un peu l'équivalent d'une exécution à choix multiple
(condition switch en C++). Chaque opérande détient une condition de garde. L'absence
de condition de garde implique une condition vraie (true). La condition else est vraie si
aucune autre condition n'est vraie. Exactement un opérande dont la condition est vraie
est exécuté. Si plusieurs opérandes prennent la valeur vraie, le choix est non
déterministe.

5.3.3 Opérateurs opt

L'opérateur option, ou opt, comporte un opérande et une condition de garde associée. Le


sous-fragment s'exécute si la condition de garde est vraie et ne s'exécute pas dans le cas
contraire.

5.3.4 Opérateur loop

Un fragment combiné de type loop possède un sous-fragment et spécifie un compte


minimum et maximum (boucle) ainsi qu'une condition de garde.
La syntaxe de la boucle est la suivante :

loop[ '('<minInt> [ ',' <maxInt> ] ')' ]

La condition de garde est placée entre crochets sur la ligne de vie. La boucle est répétée
au moins minInt fois avant qu'une éventuelle condition de garde booléenne ne soit
testée. Tant que la condition est vraie, la boucle continue, au plus maxInt fois. Cette
syntaxe peut être remplacée par une indication intelligible comme sur la figure 5.9.

65
5.3.5 Opérateur par

Figure 5.10 : Microwave est un exemple d'objet effectuant deux tâches en parallèle.

Un fragment combiné de type parallel, ou par, possède au moins deux sous-fragments


exécutés simultanément (cf. figure 5.10). La concurrence est logique et n'est pas
nécessairement physique : les exécutions concurrentes peuvent s'entrelacer sur un
même chemin d'exécution dans la pratique.

5.3.6 Opérateur strict

Figure 5.11 : Procédures de décollage d'un avion dans l'ordre.

Un fragment combiné de type strict sequencing, ou strict, possède au moins deux sous-
fragments. Ceux-ci s'exécutent selon leur ordre d'apparition au sein du fragment
combiné. Ce fragment combiné est utile surtout lorsque deux parties d'un diagramme
n'ont pas de ligne de vie en commun (ce n’est pas le cas sur la figure 5.11).

66
5.4 Utilisation d'interaction
Il est possible de faire référence à une interaction (on appelle cela une utilisation
d'interaction) dans la définition d'une autre interaction. Comme pour toute référence
modulaire, cela permet la réutilisation d'une définition dans de nombreux contextes
différents.
Lorsqu'une utilisation d'interaction s'exécute, elle produit le même effet que l'exécution
d'une interaction référencée avec la substitution des arguments fournie dans le cadre de
l'utilisation de l'interaction. L'utilisation de l'interaction doit couvrir toutes les lignes de
vie qui apparaissent dans l'interaction référencée. L'interaction référencée ne peut
ajouter des lignes de vie que si elles ont lieu en son sein.
Graphiquement, une utilisation apparaît dans un diagramme de séquence sous forme de
rectangle avec le tag ref (pour référence). On place dans le rectangle le nom de
l'interaction référencée (cf. figure 5.11). La syntaxe complète pour spécifier l'interaction
à réutiliser est la suivante :

[ <nomAttributValeurRetour> '=' ] <nomInteraction>


[ '(' [<arguments>] ')' ][ ':' <valeurRetour> ]

67
6 Diagramme de communication

Figure 6.1 : Diagramme de communication illustrant la recherche puis l'ajout, dans son
panier virtuel, d'un livre lors d'une commande sur Internet.

6.1 Représentation des lignes de vie


Les lignes de vie sont représentées par des rectangles contenant une étiquette dont la
syntaxe est :

[<nom_du_rôle>] : [<Nom_du_type>]

Au moins un des deux noms doit être spécifié dans l'étiquette, les deux points (:) sont,
quant à eux, obligatoires.

6.2 Représentation des connecteurs


Les relations entre les lignes de vie sont appelées connecteurs et se représentent par un
trait plein reliant deux lignes de vie et dont les extrémités peuvent être ornées de
multiplicités.

6.3 Représentation des messages


Dans un diagramme de communication, les messages sont généralement ordonnés selon
un numéro de séquence croissant.
Un message est, habituellement, spécifié sous la forme suivante :

[ '['<cond>']' [<séq>] [ *[||] ['['<iter>']'] ] :] [<var> :=] <msg>([<par>])

. <cond> est une condition sous forme d'expression booléenne entre crochets.
. <seq> est le numéro de séquence du message. On numérote les messages par envoi et
sous-envoi désignés par des chiffres séparés par des points : ainsi l'envoi du message
1.4.4 est postérieur à celui du message 1.4.3, tous deux étant des conséquences ([Link]
sous-envois) de la réception d'un message 1.4. La simultanéité d'un envoi est désignée
par une lettre : les messages 1.6aet 1.6b sont envoyés en même temps.

68
. <iter> spécifie (en langage naturel, entre crochets) l'envoi séquentiel (ou en parallèle,
avec ||) de plusieurs messages. On peut omettre cette spécification et ne garder que le
caractère * (ou *||) pour désigner un message récurrent, envoyé un certain nombre de
fois.
. <var> est la valeur de retour du message, qui sera par exemple transmise en
paramètre à un autre message.
.<msg> est le nom du message.
.<par> désigne les paramètres (optionnels) du message.

Cette syntaxe un peu complexe permet de préciser parfaitement l'ordonnancement et la


synchronisation des messages entre les objets du diagramme de communication (cf.
figure 6.1). La direction d'un message est spécifiée par une flèche pointant vers l'un ou
l'autre des objets de l'interaction, reliés par ailleurs avec un trait continu (connecteur).

69
7 OCL (Object Constraint Langage)
7 OCL (Object Constraint Langage) .................................................................... 70
7.1 Expression des contraintes en UML ........................................................... 71
7.1.1 Introduction .................................................................................... 71
7.1.2 Écriture des contraintes .................................................................... 72
7.1.3 Représentation des contraintes et contraintes prédéfinies ..................... 72
7.2 Intérêt d'OCL ......................................................................................... 73
7.2.1 OCL - Introduction ........................................................................... 73
QuesacOCL ? ............................................................................................. 73
Pourquoi OCL ? .......................................................................................... 74
7.2.2 Illustration par l'exemple .................................................................. 74
Mise en situation ........................................................................................ 74
Diagramme de classes ................................................................................ 75
7.3 Typologie des contraintes OCL .................................................................. 77
7.3.1 Diagramme support des exemples illustratifs ....................................... 77
7.3.2 Contexte (context) ........................................................................... 77
Syntaxe .................................................................................................... 77
Exemple ................................................................................................... 77
7.3.3 Invariants (inv)................................................................................ 78
Syntaxe .................................................................................................... 78
Exemple ................................................................................................... 78
7.3.4 Préconditions et postconditions (pre, post) .......................................... 78
Syntaxe .................................................................................................... 78
Exemple ................................................................................................... 78
7.3.5 Résultat d'une méthode (body) .......................................................... 79
Syntaxe .................................................................................................... 79
Exemple ................................................................................................... 79
7.3.6 Définition d'attributs et de méthodes (def et let…in) ............................. 79
Syntaxe de let…in ...................................................................................... 79
Syntaxe de def .......................................................................................... 79
Exemple ................................................................................................... 80
7.3.7 Initialisation (init) et évolution des attributs (derive) ............................ 80
Syntaxe .................................................................................................... 80
Exemple ................................................................................................... 80
7.4 Types et opérations utilisables dans les expressions OCL ............................. 81
7.4.1 Types et opérateurs prédéfinis ........................................................... 81
7.4.2 Types du modèle UML ....................................................................... 81
7.4.3 OCL est un langage typé ................................................................... 82
7.4.4 Collections ...................................................................................... 82
7.5 Accès aux caractéristiques et aux objets .................................................... 82

70
7.5.1 Accès aux attributs et aux opérations (self) ......................................... 83
7.5.2 Navigation via une association ........................................................... 83
7.5.3 Navigation via une association qualifiée .............................................. 84
7.5.4 Navigation vers une classe association ................................................ 85
7.5.5 Navigation depuis une classe association............................................. 85
7.5.6 Accéder à une caractéristique redéfinie (oclAsType()) ........................... 85
7.5.7 Opérations prédéfinies sur tous les objets ........................................... 85
Opération oclIsTypeOf ................................................................................ 86
Opération oclIsKindOf ................................................................................. 86
Opération oclIsNew .................................................................................... 86
Opération oclInState .................................................................................. 86
7.5.8 Opération sur les classes................................................................... 86
7.6 Opérations sur les collections ................................................................... 87
7.6.1 Introduction : « . », « -> », « :: » et self ............................................ 87
7.6.2 Opérations de base sur les collections ................................................. 87
Opérations de base sur les collections ........................................................... 87
Opérations de base sur les ensembles (Set) .................................................. 88
Exemples .................................................................................................. 89
7.6.3 Opération sur les éléments d'une collection ......................................... 89
Syntaxe générale ....................................................................................... 89
Opération select et reject ............................................................................ 89
Opération forAll et exists............................................................................. 90
Opération collect ........................................................................................ 90
7.6.4 Règles de précédence des opérateurs ................................................. 91
7.7 Exemples de contraintes .......................................................................... 91

7.1 Expression des contraintes en UML

7.1.1 Introduction

Nous avons déjà vu comment exprimer certaines formes de contraintes avec UML :

Contraintes structurelles : les attributs dans les classes, les différents types de relations
entre classes (généralisation, association, agrégation, composition, dépendance), la
cardinalité et la navigabilité des propriétés structurelles, etc. ;

Contraintes de type : typage des propriétés, etc. ;

Contraintes diverses : les contraintes de visibilité, les méthodes et classes abstraites


(contrainte abstract), etc.

71
Dans la pratique, toutes ces contraintes sont très utiles, mais se révèlent insuffisantes.
Toutefois, UML permet de spécifier explicitement des contraintes particulières sur des
éléments de modèle.

7.1.2 Écriture des contraintes

Une contrainte constitue une condition ou une restriction sémantique exprimée sous
forme d'instruction dans un langage textuel qui peut être naturel ou formel. En général,
une contrainte peut être attachée à n'importe quel élément de modèle ou liste d'éléments
de modèle. Une contrainte désigne une restriction qui doit être appliquée par une
implémentation correcte du système.
On représente une contrainte sous la forme d'une chaîne de texte placée entre
accolades ({}). La chaîne constitue le corps écrit dans un langage de contrainte qui peut
être : naturel, dédié (comme OCL) ou encore directement issu d'un langage de
programmation.

Si une contrainte possède un nom, on présente celui-ci sous forme d'une chaîne suivie
d'un double point (:), le tout précédant le texte de la contrainte.

7.1.3 Représentation des contraintes et contraintes prédéfinies

Figure 7.1 : UML permet d'associer une contrainte à un élément de modèle de plusieurs
façons.

Sur les deux diagrammes du haut, la contrainte porte sur un attribut qui doit être positif.
En bas à gauche, la contrainte {frozen} précise que le nombre de roues d'un véhicule ne
peut pas varier. Au milieu, la contrainte {subset} précise que le président est également
un membre du comité. Enfin, en bas à droite, la contrainte {xor} (ouexclusif) précise que
les employés de l'hôtel n'ont pas le droit de prendre une chambre dans ce même hôtel.

72
Figure 7.2 : Ce diagramme exprime que : une personne est née dans un pays, et que
cette association ne peut être modifiée ; une personne a visité un certain nombre de
pays, dans un ordre donné, et que le nombre de pays visités ne peut que croître ; une
personne aimerait encore visiter toute une liste de pays, et que cette liste est ordonnée
(probablement par ordre de préférence).

UML permet d'associer une contrainte à un ou plusieurs élément(s) de modèle de


différentes façons (cf. figure 7.1) :
en plaçant directement la contrainte à côté d'une propriété ou d'une opération
dans un classeur ;
en ajoutant une note associée à l'élément à contraindre ;
en plaçant la contrainte à proximité de l'élément à contraindre, comme une
extrémité d'association par exemple ;
en plaçant la contrainte sur une flèche en pointillés joignant les deux éléments de
modèle à contraindre ensemble, la direction de la flèche constituant une information
pertinente au sein de la contrainte ;
en plaçant la contrainte sur un trait en pointillés joignant les deux éléments de
modèle à contraindre ensemble dans le cas où la contrainte est bijective ;
en utilisant une note reliée, par des traits en pointillés, à chacun des éléments de
modèle, subissant la contrainte commune, quand cette contrainte s'applique sur plus de
deux éléments de modèle.

Nous venons de voir, au travers des exemples de la figure 7.1, quelques contraintes
prédéfinies ({frozen}, {subset} et {xor}). Le diagramme de la figure 7.2 en introduit
deux nouvelles : {ordered} et {addOnly}. La liste est encore longue, mais le pouvoir
expressif de ces contraintes reste insuffisant comme nous le verrons par la suite. Le
langage de contraintes objet OCL apporte une solution élégante à cette insuffisance.

7.2 Intérêt d'OCL

7.2.1 OCL - Introduction

QuesacOCL ?

C'est avec OCL (Object Constraint Language) qu'UML formalise l'expression des
contraintes. Il s'agit donc d'un langage formel d'expression de contraintes bien adapté
aux diagrammes d'UML, et en particulier au diagramme de classes.
OCL existe depuis la version 1.1 d'UML et est une contribution d'IBM. OCL fait partie
intégrante de la norme UML depuis la version 1.3 d'UML. Dans le cadre d'UML 2.0, les
spécifications du langage OCL figurent dans un document indépendant de la norme
d'UML, décrivant en détail la syntaxe formelle et la façon d'utiliser ce langage.
OCL peut s'appliquer sur la plupart des diagrammes d'UML et permet de spécifier des
contraintes sur l'état d'un objet ou d'un ensemble d'objets comme :
des invariants sur des classes ;
des préconditions et des postconditions à l'exécution d'opérations :
les préconditions doivent être vérifiées avant l'exécution,

73
les postconditions doivent être vérifiées après l'exécution ;
des gardes sur des transitions de diagrammes d'états-transitions ou des messages
de diagrammes d'interaction ;
des ensembles d'objets destinataires pour un envoi de message ;
des attributs dérivés, etc.

Pourquoi OCL ?

Nous avons dit que les contraintes pouvaient être écrites en langage naturel, alors
pourquoi s'embarrasser du langage OCL ? L'intérêt du langage naturel est qu'il est simple
à mettre en œuvre et compréhensible par tous. Par contre (et comme toujours), il est
ambigu et imprécis, il rend difficile l'expression des contraintes complexes et ne facilite
pas les références à d'autres éléments (autres que celui sur lequel porte la contrainte) du
modèle.
OCL est un langage formel volontairement simple d'accès. Il possède une grammaire
élémentaire (OCL peut être interprété par des outils) que nous décrirons dans les
sections suivantes. OCL représente, en fait, un juste milieu entre le langage naturel et un
langage très technique (langage mathématique, informatique…). Il permet ainsi de
limiter les ambiguïtés, tout en restant accessible.

7.2.2 Illustration par l'exemple

Mise en situation

Plaçons-nous dans le contexte d'une application bancaire. Il nous faut donc gérer :
des comptes bancaires ;
des clients ;
et des banques.

De plus, on aimerait intégrer les contraintes suivantes dans notre modèle :


un compte doit avoir un solde toujours positif ;
un client peut posséder plusieurs comptes ;
une personne peut être cliente de plusieurs banques ;
un client d'une banque possède au moins un compte dans cette banque ;
un compte appartient forcément à un client ;
une banque gère plusieurs comptes ;
une banque possède plusieurs clients.

74
Diagramme de classes

Figure 7.3 : Diagramme de classes modélisant une banque, ses clients et leurs comptes.

La figure 7.3 montre un diagramme de classes correspondant à la problématique que


nous venons de décrire.

Figure 7.4 : Ajout d'une contrainte sur le diagramme de la figure 7.3.

Un premier problème apparaît immédiatement : rien ne spécifie, dans ce diagramme,


que le solde du client doit toujours être positif. Pour résoudre le problème, on peut
simplement ajouter une note précisant cette contrainte ({solde > 0}), comme le montre
la figure 7.4.

Figure 7.5 : Diagramme d'objets cohérent avec le diagramme de classes de la figure 7.4.

75
Figure 7.6 : Diagramme d'objets cohérent avec le diagramme de classes de la figure 7.4,
mais représentant une situation inacceptable.

Cependant, d'autres problèmes subsistent. La figure 7.6 montre un diagramme d'objets


valide vis-à-vis du diagramme de classes de la figure 7.4 mais ne respectant pas la
spécification du problème. En effet, ce diagramme d'objets montre une personne P1
ayant un compte dans une banque sans en être client. Ce diagramme montre également
un client P2 d'une banque n'y possédant pas de compte.

context Compte
inv : solde > 0

context Compte :: débiter(somme : int)


pre : somme > 0
post : solde = solde@pre - somme

context Compte
inv : [Link] -> includes (propriétaire)

Figure 7.7 : Exemple d'utilisation du langage de contrainte OCL sur l'exemple bancaire.

Le langage OCL est particulièrement adapté à la spécification de ce type de contrainte. La


figure 7.7 montre le diagramme de classes de notre application bancaire accompagné des
contraintes OCL adaptées à la spécification du problème.
Faites bien attention au fait qu'une expression OCL décrit une contrainte à respecter et
ne décrit absolument pas l'implémentation d'une méthode.

76
7.3 Typologie des contraintes OCL

7.3.1 Diagramme support des exemples illustratifs

Figure 7.8 : Diagramme de classes modélisant une entreprise et des personnes.

Le diagramme de la figure 7.8 modélise des personnes, leurs liens de parenté


(enfant/parent et mari/femme) et le poste éventuel de ces personnes dans une société.
Ce diagramme nous servira de support aux différents exemples de contraintes que nous
donnerons, à titre d'illustration, dans les sections qui suivent.

7.3.2 Contexte (context)

Une contrainte est toujours associée à un élément de modèle. C'est cet élément qui
constitue le contexte de la contrainte. Il existe deux manières pour spécifier le contexte
d'une contrainte OCL :
en écrivant la contrainte entre accolades dans une note (comme nous l'avons fait
sur la figure 7.4). L'élément pointé par la note est alors le contexte de la contrainte ;
en utilisant le mot-clef context dans un document accompagnant le diagramme
(comme nous l'avons fait sur la figure 7.7).

Syntaxe

context <élément>

<élément> peut être une classe, une opération, etc. Pour faire référence à un
élément op (comme un opération), il faut utiliser les :: comme séparateur
(comme C::op).

Exemple

Le contexte est la classe Compte :


context Compte

Le contexte est l'opération getSolde() de la classe Compte :


context Compte::getSolde()

77
7.3.3 Invariants (inv)

Un invariant exprime une contrainte prédicative sur un objet, ou un groupe d'objets, qui
doit être respectée en permanence.

Syntaxe

inv : <expression_logique>

<expression_logique> est une expression logique qui doit toujours être vraie.

Exemple

Le solde d'un compte doit toujours être positif.


context Compte
inv : solde > 0

Les femmes (au sens de l'association) des personnes doivent être des femmes (au sens
du genre).
context Personne
inv : femme->forAll(genre=Genre::femme)

self et forAll() sont décrits après

7.3.4 Préconditions et postconditions (pre, post)

Une précondition (respectivement une postcondition) permet de spécifier une contrainte


prédicative qui doit être vérifiée avant (respectivement après) l'appel d'une opération.
Dans l'expression de la contrainte de la postcondition, deux éléments particuliers sont
utilisables :
l'attribut result qui désigne la valeur retournée par l'opération ;
et <nom_attribut>@pre qui désigne la valeur de l'attribut <nom_attribut> avant
l'appel de l'opération.

Syntaxe

Précondition :
pre : <expression_logique>

Postcondition :
post : <expression_logique>

<expression_logique> est une expression logique qui doit toujours être vraie (pour
respecter la contrainte).

Exemple

Concernant la méthode débiter de la classe Compte, la somme à débiter doit être positive
pour que l'appel de l'opération soit valide et, après l'exécution de l'opération,
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 : Real)
pre : somme > 0
post : solde = solde@pre - somme

78
Le résultat de l'appel de l'opération getSolde doit être égal à l'attribut solde.
context Compte::getSolde() : Real
post : result = solde

Même si cela peut sembler être le cas dans ces exemples, nous n'avons pas décrit
comment l'opération est réalisée, mais seulement les contraintes sur l'état avant et après
son exécution.

7.3.5 Résultat d'une méthode (body)

Ce type de contrainte permet de définir directement le résultat d'une opération.

Syntaxe

body : <requête>

<requête> est une expression qui retourne un résultat dont le type doit être compatible
avec le type du résultat de l'opération désignée par le contexte.

Exemple

Voici une autre solution au deuxième exemple de la section 7.3.4 : le résultat de l'appel
de l'opération getSolde doit être égal à l'attribut solde.
context Compte::getSolde() : Real
body : solde

7.3.6 Définition d'attributs et de méthodes (def et let…in)

Parfois, une sous-expression est utilisée plusieurs fois dans une expression. let permet
de déclarer et de définir la valeur (i.e. initialiser) d'un attribut qui pourra être utilisé dans
l'expression qui suit le in.
def est un type de contrainte qui permet de déclarer et de définir la valeur d'attributs
comme la séquence let…in. def permet également de déclarer et de définir la valeur
retournée par une opération interne à la contrainte.

Syntaxe de let…in

let <déclaration> = <requête> in <expression>

Un nouvel attribut déclaré dans <déclaration> aura la valeur retournée par


l'expression <requête> dans toute l'expression <expression>.
Reportez-vous à la section 7.7 pour un exemple d'utilisation.

Syntaxe de def

def : <déclaration> = <requête>


<déclaration> peut correspondre à la déclaration d'un attribut ou d'une
méthode. <requête> est une expression qui retourne un résultat dont le type doit être
compatible avec le type de l'attribut, ou de la méthode, déclaré dans <déclaration>.
Dans le cas où il s'agit d'une méthode, <requête> peut utiliser les paramètres spécifiés
dans la déclaration de la méthode.

79
Exemple

Pour imposer qu'une personne majeure doit avoir de l'argent, on peut écrire
indifféremment :
context Personne
inv : let argent=[Link]->sum() in age>=18 implies argent>0

ou

context Personne
def : argent : int = [Link]->sum()

context Personne
inv : age>=18 implies argent>0

sum() est décrit plus loin.

7.3.7 Initialisation (init) et évolution des attributs (derive)

Le type de contrainte init permet de préciser la valeur initiale d'un attribut ou d'une
terminaison d'association.
Les diagrammes d'UML définissent parfois des attributs ou des associations dérivées. La
valeur de tels éléments est toujours déterminée en fonctions d'autres éléments du
diagramme. Le type de contrainte derive permet de préciser comment la valeur de ce
type d'élément évolue.
Notez bien la différence entre ces deux types de contraintes. La contrainte derive impose
une contrainte perpétuelle : l'élément dérivé doit toujours avoir la valeur imposée par
l'expression de la contrainte derive. D'un autre côté, la contrainte init ne s'applique qu'au
moment de la création d'une instance précisée par le contexte de la contrainte. Ensuite,
la valeur de l'élément peut fluctuer indépendamment de la contrainte init.

Syntaxe

init : <requête>
derive : <requête>

Exemple

Quand on crée une personne, la valeur initiale de l'attribut marié est faux et la personne
ne possède pas d'employeur :
context Personne::marié : Boolean
init : false

context Personne::employeur : Set(Société)


init : Set{}

Les collections (dont Set est une instance) sont décrites par la suite. Set{} correspond à
un ensemble vide.

L'âge d'une personne est la différence entre la date courante et la date de naissance de
la personne :
context Personne::age : Integer
derive : date_de_naissance - Date::current()

80
On suppose ici que le type Date possède une méthode de classe permettant de connaître
la date courante et que l'opération moins (-) entre deux dates est bien définie et
retourne un nombre d'années.

7.4 Types et opérations utilisables dans les expressions


OCL

7.4.1 Types et opérateurs prédéfinis

Le langage OCL possède un certain nombre de types prédéfinis et d'opérations


prédéfinies sur ces types. Ces types et ces opérations sont utilisables dans n'importe
quelle contrainte et sont indépendants du modèle auquel sont rattachées ces contraintes.
Le tableau 7.1 donne un aperçu des types et opérations prédéfinis dans les contraintes
OCL. Les tableaux 7.2 rappellent les conventions d'interprétation des opérateurs
logiques.

L'opérateur logique if-then-else-endif est un peu particulier. Sa syntaxe est la suivante :

if <expression_logique_0>
then <expression_logique_1>
else <expression_logique_2>
endif

Cet opérateur s'interprète de la façon suivante : si <expression_logique_0> est vrai,


alors la valeur de vérité de l'expression est celle de <expression_logique_1>, sinon, c'est
celle de <expression_logique_2>.

Type Exemples de valeurs Opérateurs


Boolean true ; false and ; or ; xor ; not ; implies ; if-then-else-endif ; …
Integer 1 ; −5 ; 2 ; 34 ; 26524 ; … * ; + ; − ; / ; abs() ; …
Real 1,5 ; 3,14 ; … * ; + ; − ; / ; abs() ; floor() ; …
String "To be or not to be …" concat() ; size() ; substring() ; …
Tableau 4.1 : Types et opérateurs prédéfinis dans les contraintes OCL

P1 P2 P1 and P2 P1 or P2 P1 xor P2 P1 implies P2 not P1


VRAI VRAI VRAI VRAI FAUX VRAI FAUX
VRAI FAUX FAUX VRAI VRAI FAUX FAUX
FAUX VRAI FAUX VRAI VRAI VRAI VRAI
FAUX FAUX FAUX FAUX FAUX VRAI VRAI
Tableau 4.2 : Résultats des principales opérations en logique

7.4.2 Types du modèle UML

Toute expression OCL est écrite dans le contexte d'un modèle UML donné. Bien entendu,
tous les classeurs de ce modèle sont des types dans les expressions OCL attachées à ce
modèle.

81
Une contrainte OCL peut référencer une valeur enumérée de la manière suivante :
<nom_type_enuméré>::valeur

Par exemple, la classe Personne possède un attribut genre de type Genre. On peut donc
écrire la contrainte :
context Personne
inv : genre = Genre::femme

Dans ce cas, toutes les personnes doivent être des femmes.

7.4.3 OCL est un langage typé

OCL est un langage typé dont les types sont organisés sous forme de hiérarchie. Cette
hiérarchie détermine comment différents types peuvent être combinés. Par exemple, il
est impossible de comparer un booléen (Boolean) avec un entier (Integer) ou une chaîne
de caractères (String). Par contre, il est possible de comparer un entier (Integer) et un
réel (Real), car le type entier est un sous-type du type réel dans la hiérarchie des types
OCL. Bien entendu, la hiérarchie des types du modèle UML est donnée par la relation de
généralisation (héritage) entre les classeurs du modèle UML.

7.4.4 Collections

OCL définit également la notion d'ensemble sous le terme générique de collection


(collection en anglais). Il existe plusieurs sous-types du type abstrait Collection :

Ensemble ( Set ) :
collection non ordonnée d'éléments uniques (i.e. pas d'élément en double) ;

Ensemble ordonné ( OrderedSet ) :


collection ordonnée d'éléments uniques ;

Sac ( Bag ) :
collection non ordonnée d'éléments identifiables (i.e. comme un ensemble, mais
pouvant comporter des doublons) ;

Séquence ( Sequence ) :
Collection ordonnée d'éléments identifiables.
Jusqu'à UML 2.0 exclu, les collections étaient toujours plates : une collection ne
pouvait pas posséder des collections comme éléments. Cette restriction n'existe plus à
partir d'UML 2.0.

7.5 Accès aux caractéristiques et aux objets


Dans une contrainte OCL associée à un objet, il est possible d'accéder aux
caractéristiques (attributs, opérations et terminaison d'association) de cet objet, et donc,
d'accéder de manière transitive à tous les objets (et leurs caractéristiques) avec lesquels
il est en relation.

82
7.5.1 Accès aux attributs et aux opérations (self)

Pour faire référence à un attribut ou une opération de l'objet désigné par le contexte, il
suffit d'utiliser le nom de cet élément. L'objet désigné par le contexte est également
accessible par l'expression self. On peut donc également utiliser la notation
pointée : self.<propriété>.
Une opération peut avoir des paramètres, il faut alors les préciser entre les parenthèses
de l'opération.
Par exemple, dans le contexte de la classe Compte, on peut utiliser les expressions
suivantes :
solde ;
[Link] ;

getSolde() ;
[Link]() ;

débiter(1000) ;
self.débiter(1000).
Lorsque la multiplicité d'un attribut, de type T, n'est pas 1 (donc s'il s'agit d'un tableau),
la référence à cet attribut est du type ensemble (i.e. Set(T)).

Dans l'exemple précédent, le résultat de l'expression self.débiter(1000) est un singleton


du type Real. Mais une opération peut comporter des paramètres définis en sortie ou en
entrée/sortie. Dans ce cas, le résultat sera un tuple contenant tous les paramètres définis
en sortie ou en entrée/sortie.
Par exemple, imaginons une opération dont la déclaration serait operation(out
param_out : Integer):Real possédant un paramètre défini en sortie param_out. Dans ce
cas, le résultat de l'expression operation(paramètre)est un tuple de la
forme (param_out : Integer, result : Real). On peut accéder aux valeurs de ce tuple de la
façon suivante :
operation(paramètre).param_out
operation(paramètre).result

7.5.2 Navigation via une association

Pour faire référence à un objet, ou un groupe d'objets, en association avec l'objet


désigné par le contexte, il suffit d'utiliser le nom de la classe associée (en minuscules) ou
le nom du rôle d'association du côté de cette classe. Quand c'est possible, il est
préférable d'utiliser le nom de rôle de l'association du côté de l'objet auquel on désire
faire référence. C'est indispensable s'il existe plusieurs associations entre l'objet désigné
par le contexte et l'objet auquel on désire accéder, ou si l'association empruntée est
réflexive.
Le type du résultat dépend de la propriété structurelle empruntée pour accéder à l'objet
référencé, et plus précisément de la multiplicité du côté de l'objet référencé, et du type
de l'objet référencé proprement dit. Si on appelle X la classe de l'objet référencé, dans le
cas d'une multiplicité de :
1, le type du résultat est X (ex. : );
* ou 0..n…, le type du résultat est Set(X) (ex. : );
* ou 0..n…, et s'il y a en plus une contrainte {ordered}, le type du résultat

est OrderedSet(X) (ex. : ).

83
Emprunter une seule propriété structurelle peut produire un résultat du
type Set (ou OrderedSet). Emprunter plusieurs propriétés structurelles peut produire un
résultat du type Bag (ou Sequence).

Par exemple, dans le contexte de la classe Société :


directeur désigne le directeur de la société (résultat de type Personne) ;
employé désigne l'ensemble des employés de la société (résultat de
type Set(Personne)) ;
employé.compte désigne l'ensemble des comptes de tous les employés de la
société (résultat de type Bag(Compte)) ;
employé.date_de_naissance désigne l'ensemble des dates de naissance des
employés de la société (résultat de type Bag(Date)).

7.5.3 Navigation via une association qualifiée

Figure 7.10 : Diagramme illustrant une association qualifiée entre une classe Banque et
une classe Personne.

Une association qualifiée utilise un ou plusieurs qualificatifs pour sélectionner des


instances de la classe cible de l'association. Pour emprunter une telle association, il est
possible de spécifier les valeurs, ou les instances, des qualificatifs en utilisant des
crochets ([]).
Plaçons-nous dans le cadre du diagramme de la figure 4.10. Dans le contexte de la
Banque, pour faire référence au nom des clients dont le compte porte le numéro
19503800, il faut écrire :
[Link][19503800].nom

Dans le cas où il y a plusieurs qualificatifs, il faut séparer chacune des valeurs par une
virgule en respectant l'ordre des qualificatifs du diagramme UML. Il n'est pas possible de
ne préciser la valeur que de certains qualificatifs en en laissant d'autres non définis. Par
contre, il est possible de ne préciser aucune valeur de qualificatif :
[Link]

Dans ce cas, le résultat sera l'ensemble des noms de tous les clients de la banque.
Ainsi, si on ne précise pas la valeur des qualificatifs en empruntant une association
qualifiée, tout se passe comme si l'association n'était pas qualifiée. Dans ce cas, faites
attention à la cardinalité de la cible qui change quand l'association n'est plus qualifiée.

84
7.5.4 Navigation vers une classe association

Pour naviguer vers une classe association, il faut utiliser la notation pointée classique en
précisant le nom de la classe association en minuscules. Par exemple, dans le contexte
de la classe Société, pour accéder au salaire de tous les employés, il faut écrire :
[Link]

Cependant, dans le cas où l'association est réflexive (c'est le cas de la classe


association Mariage), il faut en plus préciser par quelle extrémité il faut emprunter
l'association. Pour cela, on précise le nom de rôle de l'une des extrémités de l'association
entre crochets ([]) derrière le nom de la classe association. Par exemple, dans le
contexte de la classe Personne (context Personne), pour accéder à la date de mariage
de toutes les femmes, il faut écrire :
[Link][femme].date

7.5.5 Navigation depuis une classe association

Il est tout à fait possible de naviguer directement depuis une classe association vers une
classe participante.
context Poste
inv : [Link]é.age > 21

Par définition même d'une classe association, naviguer depuis une classe association vers
une classe participante produit toujours comme résultat un objet unique. Par exemple,
l'expression [Link]é.age de l'exemple précédant produit bien un singleton.

7.5.6 Accéder à une caractéristique redéfinie (oclAsType())

Quand une caractéristique définie dans une classe parente est redéfinie dans une sous-
classe associée, la caractéristique de la classe parente reste accessible dans la sous-
classe en utilisant l'expression oclAsType().
Supposons une classe B héritant d'une classe A et une propriété p1 définie dans les deux
classes. Dans le contexte de la classe B, pour accéder à la propriété p1 de B, on écrit
simplement :
self.p1

et pour accéder à la propriété p1 de A (toujours dans le contexte de B), il faut écrire :


[Link](A).p1

7.5.7 Opérations prédéfinies sur tous les objets

L'opération oclAsType, que nous venons de décrire est une opération prédéfinie dans le
langage OCL qui peut être appliquée à tout objet. Le langage OCL en propose plusieurs :

oclIsTypeOf (t : OclType) : Boolean


oclIsKindOf (t : OclType) : Boolean
oclInState (s : OclState) : Boolean
oclIsNew () : Boolean
oclAsType (t : OclType) : instance of OclType

85
Opération oclIsTypeOf

oclIsTypeOf retourne vrai si le type de l'objet au titre duquel cette opération est invoquée
est exactement le même que le type t passé en paramètre. Par exemple, dans le
contexte de Société, l'expression [Link](Personne) est vraie tandis que
l'expression [Link](Personne) est fausse.

Opération oclIsKindOf

oclIsKindOf permet de déterminer si le type t passé en paramètre correspond exactement


au type ou à un type parent du type de l'objet au titre duquel cette opération est
invoquée.
Par exemple, supposons une classe B héritant d'une classe A :
dans le contexte de B, l'expression [Link](B) est vraie ;
toujours dans le contexte de B, l'expression [Link](A) est vraie ;
mais dans le contexte de A, l'expression [Link](B) est fausse.

Opération oclIsNew

L'opération oclIsNew doit être utilisée dans une postcondition. Elle est vraie quand l'objet
au titre duquel elle est invoquée est créé pendant l'opération (i.e. l'objet n'existait pas au
moment des préconditions).

Opération oclInState

Cette opération est utilisée dans un diagramme d'états-transitions. Elle est vraie si l'objet
décrit par le diagramme d'états-transitions est dans l'état passé en paramètre. Les
valeurs possibles du paramètre s sont les noms des états du diagramme d'états-
transitions. On peut faire référence à un état imbriqué en utilisant des «::» (par
exemple, pour faire référence à un état B imbriqué dans un état A, on écrit : A::B).

7.5.8 Opération sur les classes

Toutes les opérations que nous avons décrites jusqu'ici s'appliquaient sur des instances
de classe. Cependant, OCL permet également d'accéder à des caractéristiques de classe
(celles qui sont soulignées dans un diagramme de classes). Pour cela, on utilise le nom
qualifié de la classe suivi d'un point puis du nom de la propriété ou de
l'opération : <nom_qualifié>.<propriété>.

Le langage OCL dispose également d'une opération prédéfinie sur les classes, les
interfaces et les énumérations (allInstances) qui retourne l'ensemble (Set) de toutes les
instances du type au titre duquel elle est invoquée, au moment où l'expression est
évaluée. Par exemple, pour désigner l'ensemble des instances de la classe personne
(type set(Personne)) on écrit :
[Link]()

86
7.6 Opérations sur les collections

7.6.1 Introduction : « . », « -> », « :: » et self

Comme nous l'avons vu dans la section précédente, pour accéder aux caractéristiques
(attributs, terminaisons d'associations, opérations) d'un objet, OCL utilise la notation
pointée : <objet>.<propriété>. Cependant, de nombreuses expressions ne produisent
pas comme résultat un objet, mais une collection. Le langage OCL propose plusieurs
opérations de base sur les collections. Pour accéder ce type d'opération, il faut, utiliser
non pas un point, mais une flèche : <collection>-><opération>. Enfin, rappelons que
pour désigner un élément dans un élément englobant on utilise les «::». En résumé :
«::»
permet de désigner un élément (comme une opération) dans un élément
englobant (comme un classeur ou un paquetage) ;
«.»
permet d'accéder à une caractéristique (attributs, terminaisons d'associations,
opérations) d'un objet ;
«->»
permet d'accéder à une caractéristique d'une collection.

Nous avons dit que l'objet désigné par le contexte est également accessible par
l'expression self. L’expression self n'est pas uniquement utilisé pour désigner le contexte
d'une contrainte dans une expression, mais également pour désigner le contexte d'une
sous-expression dans le texte (en langage naturel). Ainsi, lorsque l'on utilise self pour
une opération <opération>, c'est pour désigner l'objet (comme une collection par
exemple) sur lequel porte l'opération. Cet objet peut être le résultat d'une opération
intermédiaire comme l'évaluation de l'expression <expression> précédant
l'opération <opération> dans l'expression complète : <expression>.<opération>.

7.6.2 Opérations de base sur les collections

Nous ne décrirons pas toutes les opérations sur les collections et ses sous-types
(ensemble…) dans cette section. Référez-vous à la documentation officielle, sur
[Link] pour plus d'exhaustivité.

Opérations de base sur les collections

Nous décrivons ici quelques opérations de base sur les collections que propose le langage
OCL.
size():Integer
retourne le nombre d'éléments (la cardinalité) de self.

includes(objet:T):Boolean
vrai si self contient l'objet objet.

excludes(objet:T):Boolean
vrai si self ne contient pas l'objet objet.

count(objet:T):Integer
retourne le nombre d'occurrences de objet dans self.

includesAll(c:Collection(T)):Boolean
vrai si self contient tous les éléments de la collection c.

87
excludesAll(c:Collection(T)):Boolean
vrai si self ne contient aucun élément de la collection c.

isEmpty()
vrai si self est vide.

notEmpty()
vrai si self n'est pas vide.
sum():T
retourne la somme des éléments de self. Les éléments de self doivent supporter
l'opérateur somme (+) et le type du résultat dépend du type des éléments.

product(c2:Collection(T2)):Set(Tuple(first:T,second:T2))
le résultat est la collection de Tuples correspondant au produit cartésien
de self (de type Collection(T)) par c2.

Opérations de base sur les ensembles (Set)

Nous décrivons ici quelques opérations de base sur les ensembles (type Set) que propose
le langage OCL.

union(set:Set(T)):Set(T)
retourne l'union de self et set.

union(bag:Bag(T)):Bag(T)
retourne l'union de self et bag.

=(set:Set(T)):Boolean
vrai si self et set contiennent les mêmes éléments.

intersection(set:Set(T)):Set(T)
intersection entre self et set.

intersection(bag:Bag(T)):Set(T)
intersection entre self et bag. (12)

including(objet:T):Set(T)
Le résultat contient tous les éléments de self plus l'objet objet.

excluding(objet:T):Set(T)
Le résultat contient tous les éléments de self sans l'objet objet.

-(set:Set(T)):Set(T)
Le résultat contient tous les éléments de self sans ceux de set.

asOrderedSet():OrderedSet(T)
permet de convertir self du type Set(T) en OrderedSet(T).

asSequence():Sequence(T)
permet de convertir self du type Set(T) en Sequence(T).

asBag():Bag(T)
permet de convertir self du type Set(T) en Bag(T).

Les sacs (type Bag) disposent d'opérations analogues.

88
Exemples

Une société a au moins un employé :


context Société
inv : [Link]é->notEmpty()

Une société possède exactement un directeur :


context Société
inv : [Link]->size()=1

Le directeur est également un employé :


context Société
inv : [Link]é->includes([Link])

7.6.3 Opération sur les éléments d'une collection

Syntaxe générale

La syntaxe d'une opération portant sur les éléments d'une collection est la suivante :
<collection> -> <opération>( <expression> )

Dans tous les cas, l'expression <expression> est évaluée pour chacun des éléments de la
collection <collection>. L'expression <expression> porte sur les caractéristiques des
éléments en les citant directement par leur nom. Le résultat dépend de
l'opération <opération>.

Parfois, dans l'expression <expression>, il est préférable de faire référence aux


caractéristiques de l'élément courant en utilisant la notation
pointée : <élément>.<propriété>. Pour cela, on doit utiliser la syntaxe suivante :
<collection> -> <opération>( <élément> | <expression> )

<élément> joue alors un rôle d'itérateur et sert de référence à l'élément courant dans
l'expression <expression>.

Il est également possible, afin d'être plus explicite, de préciser le type de cet élément :
<collection> -> <opération>( <élément> : <Type> | <expression> )

La syntaxe générale d'une opération portant sur les éléments d'une collection est donc la
suivante :
<collection> -> <opération>( [ <élément> [ : <Type> ] | ] <expression> )

Opération select et reject

Ces deux opérations permettent de générer une sous-collection en filtrant les éléments
de la collection self. Leur syntaxe est la suivante :
select( [ <élément> [ : <Type> ] | ] <expression_logique> )
reject( [ <élément> [ : <Type> ] | ] <expression_logique> )

select
permet de générer une sous-collection de self ne contenant que des éléments qui
satisfont l'expression logique <expression_logique>.

reject
permet de générer une sous-collection contenant tous les éléments
de self excepté ceux qui satisfont l'expression logique <expression_logique>.

89
Par exemple, pour écrire une contrainte imposant que toute société doit posséder, parmi
ses employés, au moins une personne de plus de 50 ans, on peut écrire indifféremment :
context Société
inv: [Link]é->select(age > 50)->notEmpty()

context Société
inv: [Link]é->select(individu | [Link] > 50)->notEmpty()

context Société
inv: [Link]é->select(individu : Personne | [Link] > 50)->notEmpty()

Opération forAll et exists

Ces deux opérations permettent de représenter le quantificateur universel (∀) et le


quantificateur existentiel (∃). Le résultat de ces opérations est donc du type Boolean.
Leur syntaxe est la suivante :
forAll( [ <élément> [ : <Type> ] | ] <expression_logique> )
exists( [ <élément> [ : <Type> ] | ] <expression_logique> )

forAll
permet d'écrire une expression logique vraie si l'expression est vraie pour tous les
éléments de self.

exists
permet d'écrire une expression logique vraie si l'expression est vraie pour au
moins un élément de self.

Par exemple, pour écrire une contrainte imposant que toute société doit posséder, parmi
ses employés, au moins une personne de plus de 50 ans, on peut écrire :
context Société
inv: [Link]é->exists(age > 50)

L'opération forAll possède une variante étendue possédant plus d'un itérateur. Dans ce
cas, chacun des itérateurs parcourra l'ensemble de la collection. Concrètement, une
opération forAll comportant deux itérateurs est équivalente à une opération forAll n'en
comportant qu'un, mais réalisée sur le produit cartésien de self par lui-même.

Par exemple, imposer qu'il n'existe pas deux instances de la classe Personne pour
lesquelles l'attribut nom a la même valeur, c'est-à-dire pour imposer que deux personnes
différentes ont un nom différent, on peut écrire indifféremment :
context Personne
inv: [Link]()->forAll(p1, p2 | p1 <> p2 implies [Link] <>
[Link])

context Personne
inv: ([Link]().product([Link]()))
->forAll(tuple | [Link] <> [Link] implies [Link] <>
[Link])

Opération collect

Cette opération permet de construire une nouvelle collection en utilisant la collection self.
La nouvelle collection construite possède le même nombre d'éléments que la
collection self, mais le type de ces éléments est généralement différent. La syntaxe de
l'opérateur collect est la suivante :

90
collect( [ <élément> [ : <Type> ] | ] <expression> )

Pour chaque élément de la collection self, l'opérateur collect évalue


l'expression <expression> sur cet élément et ajoute le résultat dans la collection
générée.
Par exemple, pour définir la collection des dates de naissance des employés d'une
société, il faut écrire, dans le contexte de la classe Société :
[Link]é->collect(date_de_naissance)

Le résultat d'une opération collect sur une collection du type Set n'est pas du
type Set, mais du type Bag. En effet, dans le cadre de notre exemple, il y aura
certainement des doublons dans les dates de naissance.

7.6.4 Règles de précédence des opérateurs

Ordre de précédence pour les opérateurs par ordre de priorité décroissante :


« ( » et « ) »
@pre
«.» et «->»
not et «-» (opérateur unaire)
«*» et «/»
«+» et «-»(opérateur binaire)
if-then-else-endif
«<», «>», «<=» et «>=»
«=» et «<>»
and, or et xor
implies

7.7 Exemples de contraintes

Figure 7.11 : Reprise du diagramme de la figure 7.8.

Dans cette section, nous allons illustrer par quelques exemples l'utilisation du langage
OCL. Nous restons toujours sur le diagramme de classes de la figure 7.8 représenté à
nouveau sur la figure 7.11 pour des raisons de proximité.

91
Dans une société, le directeur est un employé, n'est pas un chômeur et doit avoir plus de
40 ans. De plus, une société possède exactement un directeur et au moins un employé.
context Société
inv :
[Link]->size()=1 and
not([Link]ômeur) and
[Link] > 40 and
[Link]é->includes([Link])

Une personne est considérée comme au chômage ssi elle possède des revenus inférieurs
à 100 €.
context Personne
inv :
let revenus : Real = [Link]->sum() in
if chômeur then
revenus < 100
else
revenus >= 100
endif

Une personne possède au plus deux parents (référencés).


context Personne
inv : parent->size()<=2

Si une personne possède deux parents, l'un est une femme et l'autre un homme.
context Personne
inv :
parent->size()=2 implies
( parent->exists(genre=Genre::homme) and
parent->exists(genre=Genre::femme) )

Tous les enfants d'une personne ont bien cette personne comme parent et inversement.
context Personne
inv : enfant->notEmpty() implies
enfant->forAll( p : Personne | [Link]->includes(self))

context Personne
inv : parent->notEmpty() implies
parent->forAll ( p : Personne | [Link]->includes (self))

Pour être marié, il faut avoir une femme ou un mari.


context Personne::marié
derive : [Link]->notEmpty() or [Link]->notEmpty()

Pour être marié, il faut avoir plus de 18 ans. Un homme est marié avec exactement une
femme et une femme avec exactement un homme.
context Personne
inv : [Link]é implies
[Link]=Genre::homme implies (
[Link]->size()=1 and
[Link]=Genre::femme)
and [Link]=Genre::femme implies (
[Link]->size()=1 and
[Link]=Genre::homme)
and [Link] >=18

92

Vous aimerez peut-être aussi