Module 4 · La conception

Les diagrammes d'interaction

Le DSS s'arrêtait à la porte du système : une flèche entrait, une réponse sortait, et ce qu'il y avait derrière restait volontairement noir. Ce module ouvre la boîte. On va regarder des objets se parler pour réaliser une seule opération, dans l'ordre exact où ils le font — et récolter, en sortant, les opérations que ton diagramme de classes attend encore.

💡 Le concept

Tous les diagrammes vus jusqu'ici décrivaient des choses : des concepts, des cas d'utilisation, des classes. Celui-ci décrit un déroulement — une exécution, message par message, comme si tu mettais un logiciel au ralenti pour voir qui parle à qui.

Et il a une particularité qu'aucun autre n'a : son vrai produit n'est pas le dessin. Ce qu'on garde d'un diagramme d'interaction, c'est la liste des messages qu'il contient, parce que chacun devient une opération dans une boîte du diagramme de classes. On le dessine pour décider, pas pour l'encadrer.

Ce que le DSS laissait en blanc

Reprends le diagramme de séquence système de l'emprunt, celui du module Cas d'utilisation & DSS. Il contenait un acteur, un rectangle nommé Système, et trois flèches entrantes :

Usager ──▶ Système
   identifierLecteur(numero)
Usager ──▶ Système
   identifierLivre(isbn)
Usager ──▶ Système
   confirmerEmprunt()

Les trois opérations système de l'emprunt, telles que la section Les opérations système les a relevées : une par flèche entrante, et rien de plus.

Ce diagramme dit ce que le système doit savoir faire. Il ne dit pas un mot de la façon dont il s'y prend, et c'était le but : à ce stade, on n'avait rien décidé. Le moment est venu de décider. Un diagramme d'interaction montre les objets qui collaborent pour réaliser UNE opération système, et les messages qu'ils s'échangent.

Retiens le mot une. On ne dessine pas « le diagramme d'interaction du système » — ça n'existe pas. On en dessine un par opération système : un pour identifierLecteur, un pour identifierLivre, un pour confirmerEmprunt. Trois flèches entrantes au DSS, trois diagrammes. C'est aussi mécanique que ça, et c'est la première chose qu'un correcteur vérifie : le message qui entre dans ton diagramme est-il bien une opération système ?

🧭 Le programme du concert et la partition

Le DSS est le programme affiché à l'entrée de la salle : on y lit que la soirée comporte trois pièces, et dans quel ordre. Il ne dit rien de ce que fait le deuxième violon à la mesure 47.

Le diagramme d'interaction est la partition d'une pièce : qui joue, quand, et quoi. Soixante musiciens sans partition, c'est une cacophonie — trente classes qui se rendent service sans plan d'appel, c'est la même chose, sauf qu'on ne s'en aperçoit qu'au moment où il faut modifier quelque chose.

UML offre deux notations pour cette même partition. Le diagramme de séquence range les messages sur un axe du temps qui descend ; le diagramme de communication les pose sur un réseau d'objets, et les numérote. Le module Le processus unifié & UML l'a déjà annoncé, et il faut le prendre au pied de la lettre : les deux contiennent exactement la même information. Ce module apprend d'abord la séquence, parce que c'est celle que le cours dessine le plus, puis montre comment la même chose s'écrit en communication.

Les pièces d'un diagramme de séquence

Un diagramme de séquence n'a que quatre pièces, et tout le reste du module n'est que leurs combinaisons. Le vocabulaire compte ici plus qu'ailleurs : un correcteur qui lit « la ligne de vie de :Livre s'arrête après le retour » sait immédiatement que la notion n'est pas acquise.

La ligne de vie est l'objet, vu sur toute la durée du scénario : une boîte en haut de la page, et sous elle un long trait pointillé qui descend. Ce trait est le temps. Rien n'y est à l'échelle — deux messages séparés d'un centimètre ne durent pas plus longtemps qu'à un millimètre : la seule chose que la verticale affirme, c'est l'ordre.

Le contenu de la boîte suit une règle qui tombe souvent, et qui tient au deux-points. :Livre se lit « un objet quelconque de la classe Livre » : le deux-points n'est pas de la ponctuation, c'est lui qui dit « instance de ». Quand il faut distinguer deux objets de la même classe, on les nomme : emprunte:Livre. Et Livre tout court, sans deux-points, ne désigne pas un objet mais la classe elle-même — on ne s'en sert que pour lui envoyer une opération de classe.

Le message est une flèche horizontale qui va de l'émetteur au destinataire et porte le nom de l'opération demandée, avec ses arguments. Le retour est ce que le destinataire renvoie. Et la barre d'activation — un rectangle étroit posé sur la ligne de vie — dit que cet objet est en train de travailler.

Cette dernière pièce est celle qu'on lit mal, alors elle mérite sa phrase : le trait pointillé dit que l'objet existe, la barre dit qu'il exécute. Un objet passe toute la page sur le diagramme ; il ne travaille que par intervalles, et chacun commence quand il reçoit un message, se termine quand il a fini de répondre.

📖 La formule

Boîte, trait, barre, message

LIGNE DE VIE
   :Livre          un objet de Livre
   emprunte:Livre  un objet nommé
   Livre           la CLASSE, pas un objet
   Usager          un acteur, sans boîte

   sous la boîte, un trait POINTILLÉ :
   c'est le TEMPS. Rien n'est à l'échelle,
   seul l'ORDRE compte.

BARRE D'ACTIVATION
   posée sur le trait = l'objet EXÉCUTE
   début : la réception ; fin : la réponse

MESSAGE
   [garde] valeur := nom(arg : Type)
      estDisponible()
      [en regle] no := emprunter(livre)

RETOUR
   flèche pointillée, portant la VALEUR

La grammaire d'un diagramme de séquence, en entier. On la reconnaît à ce que tout y est facultatif sauf le nom du message : la garde, la valeur de retour et même les arguments s'omettent dès qu'ils vont de soi.

🧠 Quiz éclair

Voici les messages d'un diagramme de séquence, dans l'ordre. Sur la ligne de vie de :Livre, où commence et où finit la barre d'activation ?

1  Usager ──▶ :ControleurEmprunt
      confirmerEmprunt()
2  :ControleurEmprunt ──▶ :Livre
      estDisponible()
3  :Livre - -▶ :ControleurEmprunt
      vrai
4  :ControleurEmprunt ──▶ :Emprunt
      «create»
5  :ControleurEmprunt - -▶ Usager
      dateRetour

Elle commence à la ligne 2, quand :Livre reçoit estDisponible(), et se termine à la ligne 3, quand il a rendu sa réponse. Elle ne couvre ni la ligne 4, ni la ligne 5.

Ce que la question teste : :Livre existe pendant tout le diagramme — son trait pointillé descend du haut en bas, comme celui des autres —, mais il ne travaille que sur un aller-retour. Deux informations différentes, deux traits différents.

Regarde au passage la barre du contrôleur : elle commence à la ligne 1 et ne s'arrête qu'à la ligne 5. C'est normal, et c'est même le signe qu'on a compris — tant qu'un objet attend la réponse d'un autre, il est toujours en train d'exécuter. Les barres s'emboîtent comme des poupées russes.

⚠️ Piège fréquent

Tracer la barre d'activation sur toute la hauteur. C'est le réflexe du débutant pressé : on épaissit la ligne de vie du haut en bas, sur tous les objets, et le diagramme redit ce qu'il disait sans barres — on ne sait plus qui a la main.

La faute inverse est plus discrète : arrêter la barre de l'appelant pendant qu'il attend. Si :ControleurEmprunt a envoyé estDisponible() et attend la réponse, il n'a pas fini son travail : sa barre continue, par-dessous celle de :Livre. Une barre ne s'interrompt qu'au moment où son objet rend la main pour de bon.

Les trois traits, et ce que chacun décide

Toutes les flèches d'un diagramme de séquence ne se valent pas, et ce qui les distingue tient à deux détails de tracé : le trait est plein ou pointillé, la pointe est un triangle plein ou deux simples traits. Trois combinaisons servent, et chacune dit quelque chose de précis.

Le message synchrone — trait plein, pointe pleine — est celui que tu traceras presque toujours. Il veut dire : l'émetteur s'arrête et attend la réponse. C'est exactement ce que fait un appel de méthode ordinaire : quand tu écris livre.estDisponible() en Java, la ligne suivante ne s'exécute pas tant que la méthode n'a pas rendu sa valeur.

Le message asynchrone — trait plein, pointe fine — dit l'inverse : l'émetteur poste son message et continue immédiatement. On s'en sert pour un traitement lancé en arrière-plan, une notification, un autre fil d'exécution. Dans un projet en Java comme celui de la session, il est rare ; il faut savoir le lire, et savoir qu'on ne le trace pas par défaut.

Le retour — trait pointillé, pointe fine — remonte vers l'appelant et porte une valeur, jamais un nom d'opération. On écrit vrai, dateRetour, montant ; écrire retournerLaDate() serait un contresens — personne n'appelle rien, quelque chose revient.

Les trois traits, entre les deux mêmes objets
:ControleurEmprunt estDisponible() ────▶ :Livre
trait plein, pointe pleine — synchrone : le contrôleur s'arrête là et attend la réponse
:ControleurEmprunt estDisponible() ────→ :Livre
trait plein, pointe fine — asynchrone : le contrôleur poste son message et continue
:ControleurEmprunt ◀- - - - vrai :Livre
trait pointillé — retour : ce que :Livre renvoie, et c'est une valeur, pas un nom d'opération
Les deux mêmes objets sur les trois lignes, et c'est le trait qui change. Les deux premières portent le même message, estDisponible(), et ne disent pourtant pas la même chose : la première fait attendre le contrôleur, la seconde le laisse filer. La troisième n'est pas un message mais ce qui revient. Le tracé suffit donc à trancher, avant même qu'on ait lu ce qui est écrit dessus — et un message asynchrone comme celui du milieu ne se justifierait pas ici, puisqu'on attend forcément de savoir si le livre est disponible.

Reste une question qui revient à chaque diagramme : faut-il tracer tous les retours ? Non, et Larman est explicite là-dessus. Un retour se note de deux façons — la flèche pointillée, ou la forme valeur := message() écrite directement sur la flèche d'appel — et on ne fait jamais les deux à la fois. Quand la réponse va de soi ou ne sert à rien de la suite, on l'omet. Ce qu'on ne peut pas omettre, c'est la valeur dont le reste du diagramme se sert : le lecteur ne saurait alors pas d'où elle sort.

⚠️ Piège fréquent

Prendre la pointe pour une coquetterie de dessinateur. On trace la même flèche partout, en supposant que l'outil choisit une apparence. Or ces deux traits pleins ne disent pas la même chose du tout : l'un bloque l'émetteur, l'autre le laisse filer.

La contradiction que ça produit se repère à l'œil nu : un message asynchrone ne peut pas avoir de retour. Si tu traces une pointe fine, puis une flèche pointillée qui revient avec une valeur, tu as écrit l'impossible — l'émetteur est déjà passé à la suite, il n'y a plus personne pour recevoir la réponse.

Naître et mourir pendant la séquence

Les objets d'un diagramme n'étaient pas tous là au début. Réaliser confirmerEmprunt(), c'est précisément fabriquer un Emprunt qui n'existait pas — et la notation a de quoi le montrer.

La création se note par un message portant le stéréotype «create», tracé non pas vers une ligne de vie mais vers la boîte de l'objet créé ; et cette boîte se dessine plus bas que les autres, à la hauteur du message qui la fait naître. C'est encore la position verticale qui parle : au-dessus de sa boîte, l'objet n'existe pas.

La destruction se note par un grand × qui ferme la ligne de vie, précédé au besoin d'un message «destroy». En Java, on ne détruit rien à la main — le ramasse-miettes s'en charge quand plus personne ne tient l'objet —, donc tu traceras rarement ce symbole. Il faut savoir le lire, et savoir qu'il signifie « à partir d'ici, cet objet n'est plus », pas « le scénario se termine ».

L'auto-appel est le troisième cas particulier : un objet s'envoie un message à lui-même. La flèche part de sa ligne de vie et y revient un peu plus bas, et une seconde barre d'activation se pose sur la première, légèrement décalée vers la droite. On s'en sert quand une opération publique délègue une partie du travail à une opération de la même classe — c'est fréquent, et c'est une façon honnête de montrer qu'un objet fait deux choses distinctes.

Reste l'acteur, et il tend un piège de vocabulaire. Il se dessine en bonhomme, sans boîte et sans deux-points, il a bien une ligne de vie — mais ce n'est pas un objet du système. Il est dehors : c'est lui qui déclenche l'opération et lui qui en reçoit le résultat, jamais lui qui exécute quoi que ce soit à l'intérieur. La question d'examen « combien d'objets ce diagramme contient-il ? » se joue exactement là.

🧠 Quiz éclair

Combien d'objets ce diagramme met-il en scène ? Et où est l'auto-appel ?

1  Client ──▶ :ControleurGuichet
      retirer(500)
2  :ControleurGuichet ──▶ :Compte
      verifierSolde(500)
3  :Compte ──▶ :Compte
      soldeDisponible()
4  :Compte - -▶ :ControleurGuichet
      vrai
5  :ControleurGuichet ──▶ :Distributeur
      delivrer(500)
6  :ControleurGuichet - -▶ Client
      recu

Trois objets : :ControleurGuichet, :Compte, :Distributeur. Il y a bien quatre lignes de vie, mais Client est un acteur — il déclenche et il reçoit, il n'exécute rien dans le système. Répondre « quatre objets » est la faute la plus courante sur cette question, et c'est aussi ce qui distingue une lecture rapide d'une lecture juste.

L'auto-appel est la ligne 3 : :Compte s'envoie soldeDisponible() à lui-même. Sur le dessin, la flèche sort de sa ligne de vie et y revient, et une deuxième barre se pose sur la sienne.

Un détail qui vaut des points : :Distributeur est une frontière — du matériel, pas une entité du domaine. Le module Le diagramme de classes & l'architecture logique a posé ce vocabulaire, et un diagramme d'interaction fait apparaître les trois stéréotypes côte à côte : le contrôleur orchestre, l'entité sait, la frontière touche le monde extérieur.

Quand le diagramme doit dire « si » et « tant que »

Tout ce qui précède décrit une exécution où chaque message part, une fois, dans l'ordre. Un vrai scénario n'est jamais aussi docile : le livre peut être indisponible, le lecteur peut emprunter trois ouvrages d'un coup. Il faut donc pouvoir écrire un si et un tant que dans un diagramme, et UML 2 le fait avec les fragments combinés.

Un fragment est un cadre tracé autour d'une portion du diagramme. En haut à gauche du cadre, un petit onglet porte l'opérateur — le mot qui dit quelle sorte de contrôle on applique. À l'intérieur, une garde entre crochets donne la condition. Et les messages, eux, ne changent pas : c'est tout l'intérêt de la notation, on entoure ce qu'on avait déjà.

OpérateurCe qu'il faitL'équivalent en code
alt plusieurs branches, séparées par un trait pointillé ; une seule s'exécute, celle dont la garde est vraie if / else if / else
opt une seule branche : elle a lieu, ou elle n'a pas lieu if sans else
loop le contenu se répète tant que la garde est vraie ; on écrit aussi loop(1,n) pour un nombre connu de tours while, for
par les branches se déroulent en même temps, dans n'importe quel ordre d'entrelacement deux fils d'exécution
ref renvoie à un autre diagramme au lieu de le redessiner un appel de méthode dont on ne montre pas le corps

Les trois premiers sont ceux qu'on trace et ceux qui tombent en examen. par ne sert que si ton système a réellement des traitements simultanés, et ref est un outil de mise en page : il évite de recopier dans dix diagrammes la même sous-séquence d'authentification.

Le même message, sous trois cadres
alt [disponible]
:ControleurEmprunt emprunter(livre) ──▶ :Livre
[else]
:ControleurEmprunt refuser() ──▶ Usager
deux branches, un trait pointillé entre elles : le livre est emprunté ou bien l'usager est refusé, jamais les deux
opt [disponible]
:ControleurEmprunt emprunter(livre) ──▶ :Livre
une seule branche, aucun séparateur : le livre est emprunté, ou rien ne se passe
loop [pour chaque livre du panier]
:ControleurEmprunt emprunter(livre) ──▶ :Livre
une seule branche encore, mais répétée : le même message part autant de fois que la garde reste vraie
emprunter(livre) est écrit à l'identique dans les trois cadres : ce qui change est le cadre, et lui seul. Dans le premier, le message est l'une des deux issues possibles ; dans le deuxième, il a lieu ou n'a pas lieu ; dans le troisième, il a lieu plusieurs fois. C'est aussi la façon la plus rapide de distinguer alt de opt sur une copie : le premier a un trait pointillé au milieu, le second n'en a pas.
🧠 Quiz éclair

Ce fragment est censé dire « si le solde suffit, on débite ; sinon, on prévient l'usager ». Qu'est-ce qui ne va pas ?

opt [solde >= montant]
      :ControleurGuichet ──▶ :Compte
         debiter()
      :ControleurGuichet ──▶ Usager
         erreur()

L'opérateur est faux. Un opt n'a qu'un seul opérande : tout ce qui est dans le cadre s'exécute ensemble quand la garde est vraie. Ici, un solde suffisant déclencherait donc le débit et le message d'erreur, et un solde insuffisant ne ferait rien du tout — l'usager ne serait jamais prévenu.

Il faut un alt, un trait pointillé entre les deux messages, et une garde par branche : [solde >= montant] au-dessus du débit, [else] au-dessus de l'erreur.

La règle qui évite la faute : compte les issues. Deux issues possibles, deux branches, donc alt. Une seule issue qui peut ne pas arriver, donc opt. Le mot « sinon » dans un énoncé est le signal : il n'y a pas de « sinon » dans un opt.

⚠️ Piège fréquent

Écrire la condition dans le nom du message. On voit régulièrement emprunterSiDisponible() — et le fragment disparaît, avec la condition qu'il portait. Le nom d'une opération dit ce qu'elle fait ; le quand appartient à la garde, et la garde appartient au cadre.

La variante de la même faute est le cadre sans garde du tout : un alt dont les branches ne portent aucun crochet n'affirme plus rien, sinon qu'il existe deux possibilités quelque part. C'est la multiplicité oubliée du module précédent, transposée : le correcteur balaie les crochets avant de lire le reste, parce que ça se vérifie sans réfléchir.

Les mêmes messages, vus en réseau

Le diagramme de communication raconte exactement la même chose que le diagramme de séquence, avec une seule différence : il n'a pas d'axe du temps. Les objets sont posés où l'on veut sur la page, reliés par des traits nus — des liens —, et chaque message s'écrit le long du lien qu'il emprunte, avec une petite flèche pour le sens.

Une question devient alors urgente : si rien n'est en haut ni en bas, comment sait-on lequel part en premier ? Par le numéro. C'est lui, et lui seul, qui porte la chronologie. Un diagramme de communication sans numéros n'est pas incomplet, il est illisible : on voit qui connaît qui, on ne voit plus rien de ce qui se passe.

Et ce numéro dit plus que l'ordre : il dit la profondeur d'appel. Le message qui déclenche tout porte 1. Ceux qui partent pendant son traitement portent 1.1, 1.2, 1.3 ; si 1.2 en déclenche un autre à son tour, ce sera 1.2.1. La numérotation est donc l'exact équivalent des barres d'activation emboîtées de la séquence — la même information, écrite au lieu d'être dessinée.

Deux marques complètent la notation. L'astérisque signale la répétition : 1.3*: emprunter(livre) remplace le cadre loop. Et la garde s'écrit entre crochets avant les deux-points : 1.3 [disponible]: emprunter(livre). Un diagramme de communication n'a pas de cadres ; tout ce que les fragments portaient se replie dans la ligne du message.

Le même échange, en séquence puis en communication
En séquence — l'ordre est la position
Usager confirmerEmprunt() ──▶ :ControleurEmprunt
:ControleurEmprunt estDisponible() ──▶ :Livre
:ControleurEmprunt «create» ──▶ :Emprunt
:ControleurEmprunt emprunter(livre) ──▶ :Livre
aucun numéro nulle part : on les lit de haut en bas, et la position est la seule chose qui dise l'ordre
En communication — l'ordre est le numéro
Usager 1 confirmerEmprunt() ▶ :ControleurEmprunt
:ControleurEmprunt 1.1 estDisponible() ▶
1.3 emprunter(livre) ▶
:Livre
:ControleurEmprunt 1.2 «create» ▶ :Emprunt
un lien par paire d'objets : celui qui va vers :Livre porte plusieurs messages, et c'est leur numéro qui les sépare dans le temps
Les mêmes messages, écrits deux fois. En séquence, chacun occupe sa ligne et l'ordre se lit de haut en bas. En communication, ils se regroupent par lien — la paire d'objets qui se parlent — et l'ordre n'est plus visible nulle part : il est entièrement dans les numéros. C'est pourquoi emprunter(livre) apparaît au-dessus de «create» alors qu'il part après : la position ne veut plus rien dire, le 1.3 le dit à sa place.
✍️ Exercice de lecture

Voici un diagramme de communication pour le retour d'un livre, tel qu'on le relèverait en le parcourant à l'œil — dans l'ordre où les liens tombent sous les yeux, pas dans l'ordre du temps.

:ControleurRetour ▶ :Emprunt
   1.2 : estEnRetard(aujourdhui)
   1.4 : cloturer()
:ControleurRetour ▶ :Amende
   1.3 [en retard] : calculer()
Usager ▶ :ControleurRetour
   1 : retournerLivre(isbn)
:ControleurRetour ▶ :RegistreEmprunts
   1.1 : trouver(isbn)

Quatre questions : (a) dans quel ordre les messages partent-ils réellement ? (b) lequel peut ne pas partir du tout, et qu'est-ce qui le dit ? (c) combien d'objets ce diagramme met-il en scène ? (d) si on redessinait la même chose en séquence, quel fragment faudrait-il, et autour de quoi ?

Voir le corrigé

(a) 1, 1.1, 1.2, 1.3, 1.4 — c'est-à-dire : l'usager rend le livre, le contrôleur retrouve l'emprunt, lui demande s'il est en retard, fait calculer l'amende le cas échéant, puis clôt l'emprunt. L'ordre d'affichage ne compte pour rien : dans un diagramme de communication, la position d'un message sur la page n'a aucune valeur. Seuls les numéros parlent.

(b) 1.3, à cause de sa garde [en retard]. C'est le seul message conditionnel du lot : tous les autres partent à chaque exécution.

(c) Quatre : :ControleurRetour, :Emprunt, :Amende et :RegistreEmprunts. Usager est un acteur, pas un objet — même piège qu'au diagramme de séquence, et il se tend aussi bien dans les deux notations.

(d) Un opt [en retard] autour du seul message calculer(). Pas un alt : rien n'est prévu quand le livre est à l'heure, il n'y a donc pas de seconde branche. Remarque au passage ce que la conversion coûte : en communication, la condition tient dans les crochets d'une ligne ; en séquence, il faut lui dessiner un cadre.

Alors, lequel des deux dessiner ? Le cours demande de savoir lire et produire les deux, et le choix se fait sur ce qu'on veut faire ressortir. La séquence gagne dès que le scénario est long ou plein de conditions : l'ordre y saute aux yeux et les fragments s'y voient. La communication gagne quand on veut voir qui est relié à qui — et c'est loin d'être un détail esthétique : chaque lien du diagramme annonce une association ou une dépendance à venir dans le diagramme de classes.

Construire le diagramme d'une opération système

Devant une copie blanche, la notation ne sert à rien si l'on ne sait pas par où commencer. Voici l'ordre des gestes, déroulé sur confirmerEmprunt() — la troisième opération système du DSS de l'emprunt.

  1. Le message initial est l'opération système. Il part de l'acteur et arrive sur une seule classe : le contrôleur. Le module Le diagramme de classes & l'architecture logique a déjà tranché ce point — une opération système arrive de l'extérieur, et l'extérieur ne connaît qu'une porte d'entrée. Tu n'as donc rien à décider ici : Usager ──▶ :ControleurEmprunt : confirmerEmprunt().
  2. Écris en français ce qu'il faut faire. Pour confirmer un emprunt : vérifier que le livre est disponible, créer l'emprunt, marquer le livre sorti, rendre la date de retour. Quatre choses. Cette liste ne se dessine pas encore, elle se pose à côté.
  3. Pour chacune, demande-toi qui possède l'information. C'est la seule question qui distribue, et elle a toujours la même forme : quel objet sait déjà ce qu'il faut savoir pour faire ça ? La disponibilité d'un livre est une affaire de livre. La date de retour est une affaire d'emprunt. Le contrôleur, lui, ne sait rien — il n'a que la liste des choses à faire.
  4. Chaque délégation devient un message, tracé du contrôleur vers l'objet qui sait. Et le dernier geste est le retour vers l'acteur : ce que le DSS avait promis qu'il recevrait, et que ton diagramme doit bien finir par produire.

Ce raisonnement — confier une responsabilité à qui détient l'information — porte un nom, il a sa semaine dans le cours, et tu viens de l'appliquer sans le connaître. Ce sont Les principes GRASP, la matière de la semaine 6.

Le résultat, une fois écrit en Java, montre à quel point la correspondance est serrée : un diagramme de séquence est le corps d'une méthode, dessiné. Ce bloc-ci est l'exact équivalent du diagramme qu'on vient de construire.

ControleurEmprunt.java
public class ControleurEmprunt {
  private RegistreEmprunts registre;

  public Date confirmerEmprunt(Lecteur lecteur, Livre livre) {
    // MESSAGE 1.1 — le contrôleur ne SAIT pas si un
    //   livre est disponible : il le demande à celui
    //   qui le sait. Un appel de méthode ordinaire est
    //   un message SYNCHRONE — la ligne d'après attend.
    if (!livre.estDisponible()) {
      return null;      // la branche [else] du alt
    }

    // MESSAGE 1.2 — «create» : l'objet naît ICI, et sa
    //   boîte se dessine plus bas que les autres.
    Emprunt emprunt = new Emprunt(lecteur, livre);

    // MESSAGE 1.3 — c'est encore le livre qui change
    //   son propre état.
    livre.emprunter();
    registre.ajouter(emprunt);

    // LE RETOUR vers l'acteur : la flèche pointillée
    //   qui remonte tout en haut du diagramme.
    return emprunt.getDateRetour();
  }
}

Chaque appel de méthode est une flèche, l'accolade de la méthode est la barre d'activation du contrôleur, et le if est le fragment alt. Une seule ligne n'apparaîtrait pas sur le diagramme : getDateRetour(), parce que le module précédent l'a rangée parmi les accesseurs triviaux qu'on ne trace pas — on écrit plutôt la valeur sur la flèche de retour.

✍️ Exercice de lecture

Un coéquipier propose ce diagramme pour l'opération système payerAmende(numeroLecteur, montant). Il n'est pas faux au sens de la notation : toutes les flèches sont bien tracées. Relève ce qui cloche quand même, et dis pourquoi.

1  Bibliothecaire ──▶ :ControleurAmende
      payerAmende(no, 5.00)
2  :ControleurAmende ──▶ :RegistreLecteurs
      trouver(no)
3  :ControleurAmende ──▶ :Lecteur
      getAmendeDue()
4  :ControleurAmende ──▶ :ControleurAmende
      soustraire(5.00)
5  :ControleurAmende ──▶ :Lecteur
      setAmendeDue(reste)
6  :ControleurAmende - -▶ Bibliothecaire
      reste
Voir le corrigé

Le contrôleur fait le calcul lui-même — lignes 3, 4 et 5. Il lit le montant dû du lecteur, le soustrait dans son coin, puis réécrit le résultat dans le lecteur. Le :Lecteur n'est plus qu'un tiroir : on lui prend une valeur, on lui en repose une autre.

La question qui tranche est celle de l'étape 3 de la méthode : qui possède l'information ? Le montant dû appartient au lecteur. C'est donc lui qui doit savoir encaisser un paiement. Les trois messages se remplacent par un seul : :ControleurAmende ──▶ :Lecteur : payer(5.00), et le lecteur renvoie ce qui reste dû.

Le symptôme se reconnaît sans réfléchir, et il vaut pour tous tes diagrammes : une paire get… puis set… sur le même objet, avec un calcul entre les deux. Chaque fois que tu la vois, le calcul est du mauvais côté de la flèche.

Deux détails à ne pas signaler comme des fautes, en revanche. L'auto-appel de la ligne 4 est parfaitement légal en notation — c'est ce qu'il calcule qui ne va pas, pas le fait de s'appeler soi-même. Et :RegistreLecteurs à la ligne 2 est un bon réflexe : on ne demande pas à la classe Lecteur de retrouver un lecteur, on le demande à l'objet dont c'est le métier de tenir la liste.

⚠️ Piège fréquent

Le contrôleur qui fait tout. C'est la faute numéro un des premiers diagrammes d'interaction, et elle se voit à la forme du dessin : toutes les flèches partent de la même ligne de vie, aucune ne va d'une entité à une autre. On appelle ça un diagramme en peigne.

Le contrôleur orchestre : il reçoit l'opération système et décide de l'ordre des choses. Il ne calcule pas, ne compare pas, ne totalise pas. Le module Le diagramme de classes & l'architecture logique avait déjà repéré son cousin — le contrôleur dont la liste d'opérations ressemble au sommaire du système. C'est la même maladie vue d'un autre diagramme, et Les principes GRASP lui donne son nom officiel.

Ce que le diagramme renvoie au diagramme de classes

On arrive à la raison d'être de tout ce qui précède. Le module Le diagramme de classes & l'architecture logique laissait une question ouverte : d'où viennent les opérations des classes ? Il donnait une réponse partielle — celles qui découlent des attributs, celles qui viennent d'une règle du métier — et renvoyait le reste à cette semaine. Voici la règle qui manquait, et elle est purement mécanique :

Tout message reçu par un objet est une opération de sa classe. Pas le message qu'il envoie : celui qu'il reçoit. La flèche pointe vers celui qui devra savoir faire.

Le geste, une fois le diagramme dessiné, se fait donc au crayon et sans réfléchir : on relève chaque flèche, on regarde où sa pointe arrive, et on inscrit une ligne dans la boîte correspondante.

Le messageReçu parLa ligne à écrire dans la boîte
confirmerEmprunt() ControleurEmprunt + confirmerEmprunt(lecteur : Lecteur, livre : Livre) : Date
estDisponible() Livre + estDisponible() : boolean
emprunter() Livre + emprunter() : void
«create» Emprunt le constructeur — on ne l'écrit pas comme une opération ordinaire

La visibilité se déduit du même dessin. Une opération que quelqu'un d'autre appelle est publique : +. Une opération qui n'apparaît que dans un auto-appel n'est utile qu'à sa propre classe, et elle peut donc être privée : -. C'est un des rares endroits du cours où une décision de conception se lit directement sur un dessin, sans avoir à en discuter.

Et les liens remontent aussi. Pour envoyer un message à un objet, il faut pouvoir l'atteindre : chaque lien de ton diagramme de communication annonce donc une association navigable ou une dépendance dans le diagramme de classes. Si :ControleurEmprunt parle à :Livre, une flèche devra exister entre les deux boîtes — et c'est aussi comme ça qu'on s'aperçoit qu'un diagramme d'interaction trop bavard produira un diagramme de classes trop couplé.

🧠 Quiz éclair

D'après ces trois messages, quelle ligne faut-il ajouter dans la boîte Lecteur, et quelle ligne ne faut-il surtout pas y mettre ?

1  :ControleurEmprunt ──▶ :Lecteur
      estEnRegle()
2  :Lecteur - -▶ :ControleurEmprunt
      vrai
3  :Lecteur ──▶ :Lecteur
      totalAmendes()

Deux lignes vont dans Lecteur, et leur visibilité diffère : + estEnRegle() : boolean, publique parce qu'une autre classe l'appelle ; et - totalAmendes() : double, privée parce qu'elle n'apparaît que dans un auto-appel — personne d'autre que le lecteur ne s'en sert.

Ce qu'il ne faut surtout pas y mettre : le retour de la ligne 2. vrai n'est pas un message, c'est une valeur qui remonte ; elle est déjà décrite par le : boolean de la première ligne. L'autre faute du même genre serait d'écrire estEnRegle() dans la boîte ControleurEmprunt sous prétexte que c'est lui qui l'envoie. La règle ne regarde jamais l'émetteur : elle regarde où la pointe arrive.

🔗 Pont — la chaîne se referme

Mets bout à bout ce que les quatre modules précédents ont produit. Le modèle du domaine a donné les concepts. Le cas d'utilisation & DSS a donné les opérations système. Le diagramme de classes a posé les boîtes, en laissant leur troisième compartiment à moitié vide. Les diagrammes d'interaction le remplissent, et la boucle se ferme : c'est le même document qui repart à chaque tour, un peu plus complet.

L'idée déborde largement d'UML. Découvrir ce qu'un composant doit savoir faire en regardant qui lui parle, c'est la manière normale de concevoir une interface — l'API d'un service web se dessine ainsi, en listant les appels que ses clients feront avant d'écrire une seule ligne du serveur. Tu ne demandes jamais à un objet ce qu'il veut offrir : tu regardes ce qu'on lui demande.

Le dessiner sur une copie, sans notes

Un mot sur la forme de ce module avant de finir, parce qu'elle n'est pas celle de l'examen. Ici, les messages s'écrivent en pile — une ligne chacun, de haut en bas —, parce qu'un diagramme à quatre colonnes ne tient pas sur un écran de téléphone. Sur ta copie, tu dessineras des colonnes. La correspondance est directe et il suffit de l'avoir vue une fois : chaque nom d'objet qui apparaît dans la pile devient une boîte en haut de la feuille, et chaque ligne de la pile devient une flèche horizontale entre deux colonnes, tracée d'autant plus bas qu'elle vient plus tard.

L'ordre des gestes compte, parce qu'il évite de tout gommer :

  1. Les lignes de vie d'abord, de gauche à droite : l'acteur, puis le contrôleur, puis les entités dans l'ordre où elles entreront en jeu. Cette disposition n'est pas obligatoire, mais elle rend les flèches presque toutes orientées vers la droite — donc lisibles.
  2. Les messages ensuite, du haut vers le bas, un par un, sans sauter de ligne. Nomme-les complètement dès le premier passage : c'est ce qu'on oublie quand le temps manque.
  3. Les barres d'activation en avant-dernier. Elles se déduisent des messages — chacune commence à une réception et finit à une réponse —, donc les tracer avant oblige à les corriger.
  4. Les cadres, les gardes et les retours pour finir. Un cadre se dessine plus facilement quand on sait déjà quels messages il doit entourer.

Puis la relecture, qui prend une minute et rapporte plus que n'importe quel raffinement. Six questions, dans cet ordre :

  1. Le premier message est-il l'opération système ? Et arrive-t-il bien sur le contrôleur ?
  2. Chaque objet porte-t-il le deux-points ? Livre sans deux-points n'est pas un objet, c'est la classe.
  3. Chaque flèche part-elle de celui qui demande ? Une flèche à l'envers change complètement qui devra savoir faire.
  4. Chaque condition est-elle dans une garde ? Et chaque garde dans un cadre ?
  5. Les barres s'emboîtent-elles ? Celle de l'appelant doit couvrir celle de l'appelé, du début à la fin.
  6. L'acteur reçoit-il quelque chose à la fin ? Le DSS lui avait promis une réponse ; ton diagramme doit la produire.
⚠️ Piège fréquent

Faire travailler l'acteur. On lui envoie afficherConfirmation(), on lui fait vérifier quelque chose, on lui donne une barre d'activation qui dure. C'est tentant parce qu'il est sur le dessin comme les autres.

Mais l'acteur est dehors : il déclenche et il reçoit, un point c'est tout. Un message qui part vers lui ne peut être qu'un retour, et ce retour porte une valeur — dateRetour —, jamais une consigne d'affichage. C'est la même règle qu'au DSS, où un message sortant décrivait ce que le système rend et non comment il l'affiche : elle ne change pas quand la boîte noire s'ouvre.

🎓 À l'examen

Cette matière est celle de la semaine 5, évaluée à l'examen intra du 24 octobre — la révision de la semaine 7 porte sur les modules 1 à 5 du cours, dont celui-ci. C'est aussi le cœur du Livrable 2 — Modèle de conception et Architecture logique, à remettre le 13 octobre (10 %) : le « modèle de conception » de son titre, ce sont ces diagrammes-ci, et le diagramme de classes qu'ils remplissent. Chez Larman, ce sont les chapitres 14 et 15.

IFT-2007 n'autorise aucun matériel. Quatre choses doivent donc être en tête, et ce sont exactement celles qu'on peut rater en ayant compris la leçon : à quoi ressemblent les trois flèches, ce que la barre d'activation dit de plus que la ligne de vie, la différence entre alt et opt, et la façon dont se numérote un diagramme de communication.

« Réalisez l'opération système enregistrerRetour(isbn) par un diagramme de séquence. »

L'énoncé le plus long du chapitre, et il se traite dans l'ordre qui rapporte des points même quand le résultat diffère du corrigé. Le message initial part de l'acteur vers le contrôleur ; écris ensuite en marge, en français, la liste de ce qu'il faut faire ; pour chaque item, demande-toi qui possède l'information et trace la délégation ; termine par le retour vers l'acteur.

Ce qui distingue une bonne copie : les entités y font quelque chose. Un diagramme où toutes les flèches partent du contrôleur montre qu'on a su dessiner mais pas concevoir — et cette faute-là se voit de loin, sans même lire les noms.

« Convertissez ce diagramme de séquence en diagramme de communication. »

Une conversion, pas une reconstruction : les objets et les messages sont les mêmes, on change seulement ce qui porte l'ordre. Pose les objets, relie ceux qui se parlent par un trait, puis recopie chaque message sur son lien avec son numéro et une flèche de sens.

Les deux points où l'on perd des points : oublier la numérotation imbriquée1.1, 1.2 pour ce qui part pendant le traitement de 1, et non 2, 3 à plat —, et laisser tomber les fragments au passage. Un loop devient une astérisque sur le numéro, une garde revient entre crochets ; rien ne doit disparaître, sinon la conversion a perdu de l'information.

« Quelle est la différence entre un message synchrone et un message asynchrone ? »

La réponse en une phrase : l'émetteur d'un message synchrone attend la réponse avant de continuer ; celui d'un message asynchrone continue immédiatement. Puis la notation : pointe pleine contre pointe fine, sur un trait plein dans les deux cas.

Ajoute la conséquence, qui montre qu'on a compris plutôt que récité : un appel de méthode ordinaire en Java est synchrone, donc c'est la pointe pleine qu'on trace par défaut — et un message asynchrone ne peut pas porter de retour, puisqu'il n'y a plus personne pour l'attendre.

À retenir

Un diagramme d'interaction ouvre la boîte noire du DSS pour une opération système, et montre les objets qui se la partagent. Il en existe deux formes qui disent la même chose — la séquence, où l'ordre se lit de haut en bas, et la communication, où il tient dans des numéros —, et savoir passer de l'une à l'autre est une compétence d'examen à part entière. Toute la notation tient dans quatre pièces : la ligne de vie, le message, le retour, la barre d'activation ; les fragments ajoutent le si et le tant que en entourant des messages qui, eux, ne changent pas.

Mais l'essentiel n'est pas le dessin. Dessiner l'interaction, c'est décider qui fait quoi, et cette décision se prend avec une seule question posée à chaque étape : qui possède déjà l'information nécessaire ? Un contrôleur d'où partent toutes les flèches est la trace visible qu'on ne se l'est pas posée. Ce que le diagramme laisse derrière lui, c'est la liste des messages reçus — et chacun devient une opération dans une boîte du diagramme de classes, qui n'est complet qu'à ce moment-là.

Et ailleurs : la question « qui envoie quoi à qui, et dans quel ordre ? » est celle qu'on se pose devant n'importe quel système à plusieurs pièces, longtemps après avoir oublié UML. C'est ce que montre une trace d'exécution, un journal de requêtes HTTP, un onglet réseau de navigateur : des messages, des attentes, des réponses. Savoir lire une chronologie d'échanges, et repérer celui qui fait le travail des autres, sert à concevoir comme à déboguer.

🗂️ L'aide-mémoire
Ce que dit la barre posée sur une ligne de vie
que l'objet exécute. Le trait pointillé, lui, dit seulement qu'il existe. Tant qu'un objet attend une réponse, sa barre continue : elles s'emboîtent
Ce qui différencie un message synchrone, un asynchrone et un retour
trait plein + pointe pleine = synchrone, l'émetteur attend · trait plein + pointe fine = asynchrone, il continue · trait pointillé = retour, et il porte une valeur
La forme complète d'un message
[garde] valeur := nom(arg : Type) — seul le nom est obligatoire
Ce que veut dire le deux-points de :Livre
« instance de ». Sans lui, Livre désigne la classe, pas un objet ; avec un nom devant, emprunte:Livre, on distingue deux objets de la même classe
alt contre opt
alt : plusieurs branches séparées par un trait pointillé, une seule s'exécute · opt : une branche unique, qui a lieu ou n'a pas lieu. Il n'y a pas de « sinon » dans un opt
Comment se numérote un diagramme de communication
1, puis 1.1 et 1.2 pour ce qui part pendant le traitement de 1. L'astérisque marque la répétition ; la garde s'écrit avant les deux-points
Ce qui porte l'ordre dans chacune des deux notations
en séquence, la position verticale — et rien d'autre n'est à l'échelle · en communication, le numéro, la position sur la page ne voulant rien dire
Ce que devient un message reçu, dans le diagramme de classes
une opération de la classe qui le reçoit — jamais de celle qui l'envoie. Publique si elle vient d'ailleurs, privée si elle ne vient que d'un auto-appel
Qui reçoit le premier message, et combien de diagrammes par cas d'utilisation
le contrôleur, et ce premier message est une opération système : il y a donc un diagramme par flèche entrante du DSS
Comment se notent la naissance et la mort d'un objet
«create» vers sa boîte, qui se dessine plus bas que les autres ; un × au bout de la ligne de vie pour la destruction