Introduction à UML pour Débutants
Introduction à UML pour Débutants
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]
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.
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.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.
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.
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.
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.
Relation d'association
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.
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).
Quand un cas n'est pas directement relié à un acteur, il est qualifié de cas d'utilisation
interne.
8
Types et représentations
• 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
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.
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.
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.
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
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 :
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.
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.
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
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.
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.
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 :
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.
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.
Les attributs, les terminaisons d'association et les méthodes constituent donc les
caractéristiques d'une classe (et de ses instances).
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.
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 # :
Private ou - :
Package ou ∼ ou rien :
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.
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.
[]:
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> ;
'':
…:
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 :
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.
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 :
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 :
inout :
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.
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.
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.
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.
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).
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.
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 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.
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é :
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é :
23
2.3.3 Association binaire et n-aire
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
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.
• 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é
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.
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.
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.
• 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.
• 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
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.
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
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
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.
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).
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.
Agrégation
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.
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).
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).
• 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
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
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.
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.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 :
Le diagramme de classes modélise les règles et le diagramme d'objets modélise des faits.
33
2.5.2 Représentation
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
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 ;
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
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.
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▲
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).
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
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
L'état final est un pseudoétat qui indique que le diagramme d'états-transitions, ou l'état
enveloppant, est terminé.
3.2 Transition
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.
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.
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.
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
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)
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).
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.
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> )
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.
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
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.
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.
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
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.
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.
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.
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).
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.
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
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.
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.
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.
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).
Un nœud final est un nœud de contrôle possédant un ou plusieurs arcs entrants et aucun
arc sortant.
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).
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é.
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.
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).
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.1 Introduction
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.
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é.
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).
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).
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
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é.
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
[<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.
60
5.2.1 Messages asynchrones
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).
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.
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).
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 :
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).
Figure 5.7 : Représentation d'un objet actif (à gauche) et d'une exécution sur un objet
passif (à droite).
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.
5.3.1 Introduction
64
5.3.2 Opérateur alt
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 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 :
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.
[<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.
. <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.
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.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. ;
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.
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.
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).
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.
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.
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.
74
Diagramme de classes
Figure 7.3 : Diagramme de classes modélisant une banque, ses clients et leurs comptes.
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.
context Compte
inv : solde > 0
context Compte
inv : [Link] -> includes (propriétaire)
Figure 7.7 : Exemple d'utilisation du langage de contrainte OCL sur l'exemple bancaire.
76
7.3 Typologie des contraintes OCL
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
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
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)
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.
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
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
Syntaxe de def
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
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
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.
if <expression_logique_0>
then <expression_logique_1>
else <expression_logique_2>
endif
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
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
Ensemble ( Set ) :
collection non ordonnée d'éléments uniques (i.e. pas d'élément en double) ;
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.
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)).
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).
Figure 7.10 : Diagramme illustrant une association qualifiée entre une classe Banque et
une classe Personne.
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]
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.
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
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 :
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
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).
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
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>.
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é.
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.
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).
88
Exemples
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>.
<é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> )
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()
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> )
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.
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
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 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