Module 3 · La conception

Le diagramme de classes & l'architecture logique

Même notation que le modèle du domaine, et un tout autre document. Celui-ci ne décrit plus le monde : il décrit le logiciel qu'on va écrire, classe par classe, trait par trait. Et chaque chose qu'il ajoute est une décision — donc quelque chose qu'on peut prendre autrement, et qu'il faudra savoir défendre.

💡 Le concept

Jusqu'ici, tout ce que tu as dessiné pouvait être validé : le client regarde le modèle du domaine et dit oui ou non, parce qu'il parle de son métier. À partir de maintenant, plus personne ne peut te dire si tu as raison. Le client n'a aucun avis sur la classe qui doit calculer un total, et il n'a pas à en avoir un.

C'est ce basculement qui définit la conception, et il change la nature de ce que tu produis : un modèle du domaine se fait valider, un diagramme de conception se défend. Toutes les règles de ce module servent la même chose — pouvoir justifier chaque ligne à quelqu'un qui aurait pu la tracer autrement.

Ce que la conception ajoute, et pourquoi c'est une décision

Le diagramme de classes de conception — on l'abrège DCC, et Larman l'appelle design class diagram — emploie exactement la même notation que le modèle du domaine. Des boîtes, des traits, des nombres aux extrémités. C'est précisément ce qui le rend piégeux : rien dans le dessin n'annonce qu'on a changé de monde.

Ce qui a changé, c'est le lecteur. La section Du domaine à la conception du module Le modèle du domaine dresse le tableau des sept différences, et sa dernière ligne explique les six autres : le modèle du domaine se montre au client, le DCC ne se montre qu'à l'équipe. On ne va pas le refaire ici. On va faire l'inverse : regarder chacun de ces ajouts comme ce qu'il est vraiment — une décision.

🧭 Le croquis et les plans d'exécution

L'architecte commence par un croquis : des volumes, des pièces, leur enchaînement. Il le montre au client, qui dit « la cuisine devrait donner sur le jardin ». Puis il fait les plans d'exécution : épaisseurs de murs, sections de poutres, positions des prises. Le client ne les regarde pas — il n'y comprendrait rien, et ce n'est pas à lui de choisir une section de poutre.

Mais l'ingénieur en structure, lui, la discutera. Ce n'est pas « plus précis » : c'est discutable par d'autres gens, sur d'autres critères. Un DCC se relit comme ça — par quelqu'un qui aurait pu le faire autrement.

Reprends la classe Emprunt du module précédent. En analyse, elle porte datePret et dateRetourPrevue, et rien d'autre. En conception, elle porte - datePret : Date. Trois choses se sont ajoutées à cette seule ligne, et chacune est un choix :

  • Le type Date — on aurait pu prendre un entier de secondes, ou trois entiers jour/mois/année. Le choix engage tout le reste : comparer deux dates, les afficher, les stocker.
  • La visibilité - — on décide que personne d'autre qu'Emprunt ne touche à cette date. Ce n'est pas une évidence : c'est l'affirmation qu'on veut pouvoir changer sa représentation plus tard sans casser personne.
  • Le fait qu'elle soit là — un attribut du domaine peut très bien ne jamais devenir un attribut de classe. S'il se recalcule à partir des autres, on écrit une opération à la place.

Voilà la vraie nature du passage à la conception. Le modèle du domaine te disait quels mots existent ; le DCC répond à des questions que le domaine ne pose pas, et à chacune il y avait plusieurs réponses acceptables. C'est aussi pour ça qu'un examen peut te demander de justifier un choix de conception, ce qu'il ne demanderait jamais d'un modèle du domaine.

Une chose, en revanche, ne change pas et ne doit pas changer : les noms. Si le métier dit « emprunt », la classe s'appelle Emprunt, du modèle du domaine jusqu'au fichier Emprunt.java. C'est tout le bénéfice d'avoir fixé le vocabulaire au début, et il se perd en une seule renommage malheureux.

⚠️ Piège fréquent

Croire que le DCC est le modèle du domaine avec des parenthèses en plus. C'est l'image qu'on se fait naturellement — recopier les boîtes, ajouter des méthodes, c'est fini. Elle est fausse dans les trois sens.

Le DCC ajoute des classes que le métier n'a jamais eues — contrôleurs, fenêtres, accès aux données. Il en retire : un concept du domaine qu'aucun cas d'utilisation n'exerce n'a aucune raison de devenir une classe. Et il modifie ce qu'il garde : une association bidirectionnelle du domaine devient très souvent unidirectionnelle en conception, parce que le logiciel n'a besoin de la parcourir que dans un sens.

La boîte, ligne par ligne

La boîte a trois compartiments : le nom, les attributs, les opérations. Tu les as déjà vus au module Le modèle du domaine, où le troisième restait vide. Ici il se remplit, et chaque ligne des deux compartiments du bas suit une grammaire précise. C'est cette grammaire que l'examen te demandera d'écrire sans hésiter — et sans notes.

📖 La formule

Visibilité, nom, type

VISIBILITÉ   +  public       accessible de partout
             -  privé        accessible dans la classe seule
             #  protégé      la classe et ses sous-classes
             ~  paquetage    les classes du même paquetage

ATTRIBUT     visibilité nom : Type [multiplicité] = valeurParDefaut
                -  solde : double = 0.0
                -  lignes : LigneCommande [1..*]

OPÉRATION    visibilité nom(param : Type, ...) : TypeRetour
                +  calculerTotal() : double
                +  ajouterLigne(plat : Plat, quantite : int) : void

             souligné = membre de CLASSE       italique = ABSTRAIT

La notation d'un membre de classe en UML. On l'écrit pour chaque ligne des deux compartiments du bas ; on la reconnaît au fait que tout y est facultatif sauf le nom et la visibilité — et que c'est la visibilité qu'on oublie.

Quelques précisions qui reviennent en examen. La multiplicité d'un attribut, entre crochets, dit qu'il en contient plusieurs : lignes : LigneCommande [1..*] annonce une collection, et c'est souvent une autre façon d'écrire une association — on y revient dans la section « La navigabilité, ou le trait qui devient un attribut ».

Le soulignement marque ce qui appartient à la classe et non aux objets — static en Java. + nombreDeComptes() : int souligné veut dire qu'on l'appelle sur la classe CompteBancaire elle-même, sans avoir de compte sous la main. L'italique, lui, marque l'abstrait : une classe en italique ne s'instancie pas, une opération en italique n'a pas de corps et doit être redéfinie plus bas. Ces deux marques sont typographiques et non symboliques, ce qui est leur seul défaut : sur une copie manuscrite, souligne franchement.

🧠 Quiz éclair

Cette boîte est censée être une classe de conception. Relève les quatre lignes qui ne respectent pas la notation.

              CompteBancaire
  -----------------------------------------
    numero : String
  - solde
  # tauxInteret : double
  -----------------------------------------
  + deposer(montant : double)
  - validerMontant(m : double) : boolean
  + nombreDeComptes : int

Ligne 1 — numero : String : la visibilité manque. C'est la faute la plus coûteuse du lot, parce qu'elle se répète : un correcteur qui la trouve une fois la cherche partout.

Ligne 2 — - solde : le type manque. En analyse, il était facultatif ; en conception, il ne l'est plus — c'est justement l'une des trois marques du logiciel.

Ligne 4 — + deposer(montant : double) : le type de retour manque. S'il n'y en a pas, on écrit : void ; le laisser en blanc veut dire « je n'ai pas décidé ».

Ligne 6 — + nombreDeComptes : int : elle est dans le compartiment des opérations mais s'écrit comme un attribut, sans parenthèses. Si c'est un compteur global, il descend d'un compartiment et se souligne ; si c'est une opération, elle prend ses parenthèses.

⚠️ Piège fréquent

Remplir le troisième compartiment d'accesseurs. Écrire + getSolde(), + setSolde(), + getNumero(), + setNumero() pour chaque attribut donne une boîte impressionnante et vide de sens : on a recopié le compartiment du milieu en deux fois plus long.

Larman le dit sans détour : les accesseurs triviaux n'ont pas leur place sur un diagramme. Le compartiment des opérations sert à montrer ce que la classe sait faireestEnRetard(), calculerTotal(), prolonger(). Si ta boîte ne contient que des get et des set, tu n'as pas encore conçu : tu as déclaré des données.

D'où viennent les opérations

Voici la question que les cours sautent presque toujours, et sur laquelle on bute dès qu'on est devant une copie blanche : qui décide qu'Emprunt a une opération estEnRetard() ? Pas le client, on vient de le voir. Pas le modèle du domaine, qui n'en porte aucune. Il y a une réponse, et elle vient en trois temps.

Premier temps : les opérations système. La section Les opérations système du module Cas d'utilisation & DSS le dit déjà : chaque flèche entrante d'un DSS est une opération que le système doit rendre, et cette liste est le contrat que la conception doit réaliser. C'est l'entrée de ce module. Si ton DSS d'emprunt donne identifierLecteur(numero), identifierLivre(isbn) et confirmerEmprunt(), tu as trois obligations, et pas une de plus.

Deuxième temps : une opération système n'est pas une méthode de classe. C'est la marche qu'on rate. confirmerEmprunt() est une opération du système entier, vu de l'extérieur comme une boîte noire. Elle doit maintenant être attribuée à une classe, qui devra en appeler d'autres pour faire le travail — et chacun de ces appels devient à son tour une opération de la classe appelée.

Troisième temps : c'est le diagramme d'interaction qui fait cette distribution. On y dessine les objets qui collaborent et les messages qu'ils s'envoient ; tout message reçu par un objet est une opération de sa classe. C'est mécanique, et c'est la seule source complète des opérations.

🧭 Un ordre qu'il faut assumer

Le cours te fait dessiner le diagramme de classes cette semaine et Les diagrammes d'interaction la semaine prochaine. Lu comme une recette, c'est illogique : on te demande les opérations avant l'outil qui les trouve.

Ce n'est pas une erreur du plan, c'est la réalité du travail. Le DCC se remplit par allers-retours : on pose d'abord les classes et les attributs, qui viennent du domaine et qu'on connaît déjà ; on ajoute les opérations qu'on peut déduire sans interaction ; puis chaque diagramme d'interaction dessiné renvoie une poignée d'opérations à inscrire dans les boîtes. Le diagramme de classes est le seul du cours qui n'est jamais « fini » avant la fin.

Reste ce qu'on peut écrire tout de suite, sans avoir tracé un seul message, et qui suffit largement pour un premier livrable :

  1. Les opérations qui découlent des attributs. Un Emprunt qui connaît sa dateRetourPrevue sait dire s'il est en retard. Une Commande qui connaît ses lignes sait calculer son total. La règle qui les trouve est simple : quelle question peut-on poser à cet objet à laquelle lui seul peut répondre ?
  2. Les opérations qui viennent d'une règle du métier. « Un lecteur qui doit plus de 5 $ ne peut pas emprunter » donne + estEnRegle() : boolean sur Lecteur. La règle existait dans l'énoncé ; elle n'avait simplement nulle part où aller en analyse.
  3. Les opérations système, posées sur un contrôleur. On leur donne une classe d'accueil — voir la section « Les classes que le métier n'a jamais eues » — quitte à ce que son contenu se précise plus tard.
✍️ Exercice de lecture

Voici les opérations système tirées du DSS « Emprunter un livre ». Pour chacune, dis quelle classe la reçoit, et nomme une opération qu'elle devra elle-même appeler.

1. identifierLecteur(numero)
2. identifierLivre(isbn)
3. confirmerEmprunt()

Classes disponibles : ControleurEmprunt, Lecteur, Livre, Emprunt
Voir le corrigé

Les trois sont reçues par ControleurEmprunt. C'est le point le plus important du corrigé : une opération système arrive de l'extérieur, et l'extérieur ne connaît qu'une seule porte d'entrée. Répartir les trois sur trois classes différentes reviendrait à donner trois portes au système, ce que le DSS interdisait déjà en n'ayant qu'un seul rectangle.

1 — identifierLecteur(numero) appelle Lecteur.estEnRegle(). Le contrôleur ne sait pas ce qu'est « être en règle » — c'est une affaire de lecteur, et le lecteur seul connaît son amende.

2 — identifierLivre(isbn) appelle Livre.estDisponible(). Même raisonnement.

3 — confirmerEmprunt() crée un Emprunt et appelle par exemple Livre.emprunter() pour le marquer sorti. C'est la seule des trois qui change quelque chose, et c'est pour ça qu'elle porte le nom d'une confirmation.

Remarque au passage ce que le corrigé vient de faire : il a distribué des responsabilités en se demandant chaque fois qui possède l'information nécessaire. Ce raisonnement a un nom et sa propre semaine de cours — Les principes GRASP. Tu l'as employé ici sans le savoir.

La navigabilité, ou le trait qui devient un attribut

Dans le modèle du domaine, une association est un fait : « un emprunt concerne un livre ». Le fait n'a pas de direction — il est vrai des deux côtés à la fois. En conception, ce confort disparaît, parce qu'un objet en mémoire ne connaît que ce qu'il garde. Il faut décider qui garde la référence de qui, et cette décision s'appelle la navigabilité.

Elle se note par une flèche ouverte à l'extrémité qu'on peut atteindre. Et il faut connaître la convention du cours avant tout le reste, parce qu'elle est contre-intuitive et qu'elle tombe en examen : une association sans aucune flèche se lit comme bidirectionnelle — chacun peut atteindre l'autre. Ne pas trancher n'est donc pas neutre : c'est choisir par défaut l'option la plus coûteuse.

La même association, trois navigabilités
Emprunt 0..* concerne 1 Livre
aucune flèche — bidirectionnel par convention : Emprunt porte livre et Livre porte une collection d'emprunts, à tenir d'accord
Emprunt 0..* concerne ▶ 1 Livre
flèche vers Livre — un seul attribut, livre : Livre, parce que l'extrémité visée porte 1 ; Livre ignore ses emprunts
Emprunt 0..* ◀ concerne 1 Livre
flèche vers Emprunt — l'extrémité visée porte 0..*, donc Livre porte une collection, et c'est l'emprunt qui ignore son livre
Les trois lignes portent les mêmes classes, le même verbe et les mêmes multiplicités : seul le sens des flèches change. Chacune produit un code différent — deux attributs à synchroniser, un attribut simple, une collection —, et c'est la multiplicité de l'extrémité visée qui décide entre un champ et une collection. La navigabilité est la seule marque du diagramme qui se traduise directement en champs de classe.

Le code rend cette traduction évidente, et c'est la raison pour laquelle ce module montre du Java alors qu'il parle de diagrammes : la flèche est l'attribut.

Emprunt.java
public class Emprunt {
  private Date datePret;
  private Date dateRetourPrevue;

  // LA FLÈCHE « Emprunt ──▶ Livre » EST CETTE LIGNE.
  // Multiplicité 1 du côté Livre : un seul champ, pas une collection.
  private Livre livre;

  // Rien dans Livre ne pointe vers Emprunt : la flèche ne va que
  // dans un sens, donc Livre ignore par quels emprunts il est passé.
  public boolean estEnRetard(Date aujourdhui) {
    return aujourdhui.after(dateRetourPrevue);
  }
}

Une association navigable donne un champ dont le type est la classe visée. C'est la seule chose que la flèche veut dire — et c'est aussi pourquoi une flèche tracée à la légère devient une dépendance qu'on traîne jusqu'au dernier livrable.

La troisième ligne de la figure mérite qu'on s'y arrête, parce qu'elle est le cas que le code rate le plus souvent. Si l'extrémité visée porte 0..* ou 1..*, l'attribut n'est plus une référence unique : c'est une collection. Une Commande navigable vers ses LigneCommande en multiplicité 1..* donne private List<LigneCommande> lignes; — et sur le diagramme, on a le droit d'écrire la même chose dans le compartiment des attributs, - lignes : LigneCommande [1..*]. Les deux notations disent exactement la même chose ; on choisit le trait quand la classe visée est importante, l'attribut quand elle est secondaire.

Comment décide-t-on du sens ? Par les besoins réels, jamais par symétrie. La question à poser est : y a-t-il un scénario où un objet de cette classe doit envoyer un message à l'autre ? Si oui, il faut la flèche ; si non, elle ne sert à rien et coûte quelque chose. Trois indices classiques qu'une flèche est nécessaire : A crée B, A doit lire ou modifier B, ou A reçoit un message qui l'y oblige.

⚠️ Piège fréquent

Mettre des flèches dans les deux sens par réflexe. Ça paraît généreux — « comme ça, tout le monde peut tout atteindre » — et c'est la décision la plus coûteuse du diagramme.

Chaque association bidirectionnelle crée deux attributs qui doivent rester d'accord. Retirer une ligne d'une commande demande alors de la retirer de la collection et d'effacer le champ qui remonte, sinon la mémoire contient deux vérités contradictoires. Elle crée aussi un cycle : les deux classes dépendent l'une de l'autre, on ne peut plus en compiler, en tester ni en réutiliser une sans l'autre. La règle pratique est la même que pour les losanges du modèle du domaine : en cas de doute, une seule flèche.

🧠 Quiz éclair

Ce code prétend réaliser l'association Commande 1 ——▶ 0..* LigneCommande. Qu'est-ce qui ne correspond pas au diagramme ?

public class Commande {
  private LigneCommande ligne;
  private Client client;
}

public class LigneCommande {
  private Commande commande;
}

Deux écarts, et le second est le plus grave.

La multiplicité n'est pas respectée. Le diagramme annonce 0..* du côté LigneCommande, donc une collection — private List<LigneCommande> lignes;. Le champ unique ligne ne peut en tenir qu'une : le code réalise en fait un 0..1.

Une flèche a été ajoutée sans être au diagramme. LigneCommande porte un champ commande, ce qui rend l'association bidirectionnelle alors qu'elle ne l'était pas. C'est exactement le piège ci-dessus, arrivé par le code au lieu du dessin — et c'est ainsi qu'il arrive le plus souvent.

Le champ client, lui, n'est pas une faute : il réalise une autre association, qui a simplement le droit d'exister. Un diagramme ne montre jamais qu'une paire de classes à la fois.

La dépendance, et ce qu'un pointillé avoue

Il existe un quatrième trait, qu'on n'a pas encore croisé parce qu'il n'a aucun sens en analyse : la dépendance. Elle se dessine par une flèche en pointillé, et elle dit quelque chose de plus faible qu'une association : « cette classe a besoin de connaître l'autre, mais elle ne la garde pas ».

Le critère est la durée. Une association vit aussi longtemps que l'objet : le champ est là, entre deux appels, tant que l'objet existe. Une dépendance ne vit que le temps d'un appel de méthode. Trois formes, et ce sont les trois que l'examen montre :

  • la classe reçoit l'autre en paramètre d'une de ses opérations ;
  • elle la retourne ;
  • elle l'utilise localement, ou appelle une de ses opérations de classe (une méthode static).
La même classe atteinte, par un trait plein puis pointillé
Emprunt ──── concerne ▶ 1 Livre
association — Emprunt garde un champ livre, disponible entre deux appels et tant que l'emprunt existe
ControleurEmprunt - - - - utilise ▷ Livre
dépendance — ControleurEmprunt reçoit un livre en paramètre, s'en sert, et n'en garde aucune trace après l'appel
Deux traits vers la même classe Livre, et ce qui les sépare est la durée du lien : le trait plein donne un attribut permanent, le trait pointillé un usage passager. Noter aussi qu'une dépendance ne porte pas de multiplicités — il n'y a rien à compter dans un lien qui ne dure pas.

Pourquoi consacrer une section à un trait aussi discret ? Parce qu'il est le lien le plus faible avec lequel deux classes peuvent encore collaborer, et que c'est exactement ce qu'on cherche. Deux classes reliées par une association sont soudées : on ne peut plus toucher à l'une sans risquer l'autre. Deux classes reliées par une dépendance ne se rencontrent que le temps d'un appel.

D'où une habitude qui vaut pour tout le reste de la session : quand une association peut se remplacer par une dépendance, on le fait. Si le contrôleur n'a besoin du livre que pendant confirmerEmprunt(), il ne doit pas le stocker ; il le reçoit, l'utilise, l'oublie. Ce réflexe porte un nom qu'on te donnera formellement en semaine 6 — le faible couplage, l'un des Les principes GRASP —, et tu peux commencer à l'appliquer avant de connaître son nom.

Les classes que le métier n'a jamais eues

Le modèle du domaine ne contenait que des concepts prononçables par le client. Le DCC en contient d'autres, et ces autres sont souvent la majorité des boîtes. Le client ne reconnaîtrait ni ControleurEmprunt, ni FenetreEmprunt, ni EmpruntDAO — et c'est très bien, parce que ce sont des réponses à des questions qu'il ne se pose pas.

Pour les ranger, UML permet de coller une étiquette sur une classe : un stéréotype, écrit entre chevrons doubles. Trois d'entre eux forment le vocabulaire de base de la conception orientée objet, hérités de l'analyse dite « robuste » d'Ivar Jacobson.

StéréotypeSon rôleExemples
«entity»
entité
porter un concept du métier et ses données : c'est ce qui survit à l'arrêt du programme Emprunt, Livre, Lecteur
«boundary»
frontière
tout ce qui touche au monde extérieur — un acteur humain, mais aussi un autre système ou un appareil FenetreEmprunt, PasserellePaiement
«controller»
contrôleur
recevoir une opération système et orchestrer les entités qui la réalisent ; il coordonne, il ne calcule pas ControleurEmprunt

Le contrôleur mérite qu'on s'y arrête, parce que c'est la classe qui répond à la question laissée ouverte plus haut : où atterrit une opération système ? Elle atterrit sur un contrôleur, et il y a deux façons d'en choisir un. Soit un contrôleur par cas d'utilisationControleurEmprunt, ControleurRetour —, soit un contrôleur unique qui représente le système entier ou l'organisation. Le premier choix est le plus sûr dès que le système a plus d'une poignée de cas d'utilisation.

La frontière est plus large que ce que son nom courant laisse croire : ce n'est pas seulement l'interface graphique. Une classe qui parle à une passerelle de paiement est une frontière, une classe qui lit un lecteur de codes-barres aussi. C'est la même idée que les acteurs non humains du module Cas d'utilisation & DSS, descendue d'un niveau.

Et il en manque une quatrième dans le trio classique, que tout projet réel possède : la persistance. Les classes en DAO (data access object) ou en Repository savent aller chercher une entité dans une base de données et l'y remettre. On les traite en pratique comme une frontière — la base est un système externe comme un autre.

Si ce trio te rappelle quelque chose, c'est normal : MVC — Modèle-Vue-Contrôleur — est l'application de ces trois rôles aux interfaces graphiques. La vue est une frontière, le modèle une entité, et le contrôleur porte le même nom des deux côtés. MVC n'est donc pas une notion de plus à apprendre : c'est le vocabulaire que le monde du logiciel a retenu de ces stéréotypes.

⚠️ Piège fréquent

Prendre un stéréotype pour un type du langage. «entity», «boundary» et «controller» ne s'écrivent nulle part en Java. Ce ne sont ni des classes à hériter, ni des interfaces à implémenter, ni des annotations : ce sont des rôles, c'est-à-dire des étiquettes que tu poses sur le diagramme pour dire ce que la classe est venue faire.

Ils guident la conception, et ils s'évaporent au moment du code — sauf si tu emploies un cadriciel qui les matérialise, ce qui n'est pas le cas ici. La marque qu'il en reste dans un projet Java est ailleurs, et elle est plus utile : c'est le paquetage dans lequel la classe se range, sujet de la section suivante.

🧠 Quiz éclair

Attribue un stéréotype à chacune de ces cinq classes, et repère celle qui pose un problème de conception.

(A) FormulaireConnexion
(B) Lecteur
(C) ControleurRetour
(D) LecteurCodeBarres
(E) ControleurPrincipal
      + emprunter()  + retourner()  + inscrire()
      + payerAmende()  + reserver()  + imprimerRapport()

(A) frontière — c'est de l'interface graphique. (B) entité — un concept du domaine, il était déjà au modèle du domaine. (C) contrôleur. (D) frontière, et c'est le piège de vocabulaire du lot : le mot « lecteur » désigne ici un appareil, pas une personne. Deux classes qui commencent par le même mot n'ont aucune raison d'être de la même famille.

(E) est le problème. C'est bien un contrôleur, mais il en fait trop : six opérations système qui viennent de six cas d'utilisation différents. Le symptôme se reconnaît sans réfléchir — un contrôleur dont la liste d'opérations ressemble au sommaire du système. Il devient le point par lequel tout passe, donc celui qu'on modifie à chaque changement.

Le remède est de le découper par cas d'utilisation : ControleurEmprunt, ControleurRetour, ControleurInscription. Ce défaut a un nom officiel et une place dans le cours — le contrôleur surchargé, en semaine 6 avec Les principes GRASP.

L'architecture logique : regrouper en paquetages

Une trentaine de classes tiennent encore sur une feuille. Deux cents, non — et le vrai problème n'est même pas le dessin : c'est qu'on ne sait plus dire ce qui dépend de quoi. L'architecture logique répond à ça. Elle est l'organisation des classes en paquetages et les dépendances entre ces paquetages, et c'est la seconde moitié de ton Livrable 2.

Le mot est resté sans définition jusqu'ici, alors même qu'il apparaissait dans le tableau de visibilité de la formule. Un paquetage est un regroupement nommé de classes — rien de plus, et c'est déjà beaucoup : il crée une frontière, donc un intérieur et un extérieur. Sa notation UML est un rectangle avec un onglet en haut à gauche, comme une chemise cartonnée, et les dépendances entre paquetages se tracent en pointillé, exactement comme entre classes.

En Java, la correspondance est directe et c'est ce qui la rend facile à retenir : un paquetage est un dossier, et chaque fichier annonce le sien par une ligne package. L'arborescence de ton dépôt est ton architecture logique.

src/
└── bibliotheque/
    ├── presentation/     FenetreEmprunt, FenetreRetour
    ├── application/      ControleurEmprunt, ControleurRetour
    ├── domaine/          Emprunt, Livre, Lecteur
    └── persistance/      EmpruntDAO, LivreDAO

Quatre paquetages, quatre dossiers, et la même chose vue de deux façons. Le diagramme de paquetages du livrable ne dit rien de plus que cette arborescence, plus les flèches qui disent qui a le droit d'appeler qui.

Le ~ de la formule prend enfin son sens : une visibilité paquetage rend un membre accessible aux classes du même paquetage et à personne d'autre. En Java, c'est ce qu'on obtient en n'écrivant aucun mot-clé devant un membre — le défaut du langage. C'est utile pour laisser deux classes voisines collaborer étroitement sans ouvrir la porte au reste du système.

Reste la question qui décide de tout : selon quoi découpe-t-on ? Deux réponses s'affrontent dans la vraie vie. Par couche technique — présentation, application, domaine, persistance — ou par fonctionnalité — emprunt, retour, inscription, chacune contenant sa fenêtre, son contrôleur et ses entités. Le cours, et Larman avec lui, enseignent le découpage par couche : c'est celui que ton livrable doit montrer, et celui que la section suivante explique.

Une seule règle est absolue, quel que soit le découpage : pas de cycle entre paquetages. Si presentation dépend de domaine et que domaine dépend de presentation, les deux ne forment plus qu'un seul bloc qu'on ne peut ni compiler, ni tester, ni comprendre séparément — et on a perdu exactement ce qu'on était venu chercher.

🔗 Pont — le paquetage est un DSS d'un cran plus bas

Le module Cas d'utilisation & DSS t'a fait traiter le système comme une boîte noire : on déclare ce qui franchit sa frontière, et ce qu'il y a derrière reste libre de changer. Un paquetage est cette même idée appliquée à l'intérieur du système.

Ses classes publiques sont ses opérations système ; le ~ et le - sont ce qui reste dans la boîte. Et le bénéfice est identique : ce qui est caché peut changer sans prévenir personne. Tu retrouveras cette idée sous d'autres noms toute ta vie — l'interface d'un module, l'API d'un service, l'en-tête d'une bibliothèque. C'est chaque fois la même : publier peu, se réserver le reste.

Les couches, et la règle qui les tient

Le découpage en couches range les paquetages en strates horizontales, de l'écran vers le disque. Quatre suffisent à décrire presque tous les systèmes de gestion, et ce sont celles que ton livrable doit nommer.

La coucheSa responsabilitéSes classes
Présentation afficher, et capter les gestes de l'utilisateur ; elle ne décide rien FenetreEmprunt, FormulaireConnexion
Application recevoir les opérations système et orchestrer ; elle ne calcule rien elle-même ControleurEmprunt
Métier porter les concepts et les règles du domaine ; c'est la couche qui contient la valeur du logiciel Emprunt, Livre, Lecteur
Persistance ranger et retrouver les entités dans une base ou des fichiers EmpruntDAO, LivreDAO

La règle qui les tient tient en une phrase : une couche n'appelle que celle qui est immédiatement sous elle. Pas au-dessus, et pas en sautant. Le corollaire le plus utile en pratique : une fenêtre ne parle jamais directement à une entité. Elle s'adresse au contrôleur, qui s'adresse aux entités — c'est ce qui permet à la même règle de tourner ensuite derrière une autre interface.

Le même appel, permis puis interdit
Présentation appelle ▶ PERMIS Application
la fenêtre appelle le contrôleur — c'est la couche juste en dessous
Présentation appelle ▶ INTERDIT Persistance
la fenêtre lit la base directement : elle saute l'application et le métier, donc les règles qu'ils portaient ne s'appliqueront pas
Persistance appelle ▶ INTERDIT Présentation
le DAO ouvre une fenêtre pour signaler une erreur : l'appel remonte, et le voilà inutilisable hors d'une interface graphique
Trois fois le même geste — une classe qui en appelle une autre — et seule la paire de couches change. Le premier appel descend d'un cran : il est permis. Le deuxième saute des couches, le troisième remonte : les deux sont refusés, pour deux raisons différentes que la prose ci-dessous nomme.

Les manuels justifient cette règle par quatre mots — découplage, testabilité, réutilisation, clarté — qui sont vrais et qui n'expliquent rien. La vraie raison est plus simple, et une fois vue elle ne se réoublie pas : appeler, c'est dépendre. Si A appelle B, alors tout changement de B peut casser A. Le sens des flèches est donc le sens dans lequel les changements se propagent.

Or les quatre couches ne changent pas à la même vitesse. L'écran change tout le temps — un client vous demandera trois refontes. La base change souvent. Les règles du métier, elles, changent rarement : une bibliothèque prête des livres pour trois semaines depuis toujours. On oriente donc les dépendances de l'instable vers le stable, jamais l'inverse. Une couche métier qui connaît la fenêtre est une règle durable rendue fragile par un écran qui ne durera pas six mois.

Le test qui vérifie tout ça en une question : peux-tu remplacer l'interface graphique par une ligne de commande sans toucher au métier ? Si oui, l'architecture tient. Si non, quelqu'un a tracé une flèche vers le haut.

⚠️ Piège fréquent

Le message d'erreur affiché depuis la couche métier. C'est la violation la plus fréquente, et la plus innocente en apparence : une classe du domaine détecte un problème et ouvre une boîte de dialogue pour le dire. Une ligne, et la flèche remonte de deux couches.

Ce qu'on vient de perdre : la classe ne peut plus tourner sans interface graphique, donc plus être testée automatiquement, plus servir dans un traitement de nuit, plus être appelée depuis une application web. Le bon geste est de renvoyer l'information — une exception, un booléen, un objet de résultat — et de laisser la couche du dessus décider comment l'annoncer. Une couche basse répond ; elle ne parle jamais à l'utilisateur.

✍️ Exercice de lecture

Place chaque classe dans sa couche, puis dis lequel des trois appels est illégal.

Classes :  FenetreCommande     ServiceCalculTaxe
           ControleurPaiement  CommandeRepository
           Commande            PasserelleStripe

Appels :  1. ControleurPaiement  →  Commande
          2. Commande            →  CommandeRepository
          3. ControleurPaiement  →  CommandeRepository
Voir le corrigé

Les couches. Présentation : FenetreCommande. Application : ControleurPaiement. Métier : Commande et ServiceCalculTaxe — le second porte une règle du domaine et aucune interface, il reste donc au métier malgré son nom en « service ». Persistance : CommandeRepository. Et PasserelleStripe est une frontière vers un système externe ; beaucoup d'équipes lui donnent sa propre couche, souvent nommée infrastructure ou intégration, précisément pour pouvoir la remplacer.

L'appel illégal est le 2. Commande est une entité du métier ; si elle appelle son repository, elle sait qu'elle est stockée et où. La règle métier devient alors inséparable de la base de données : on ne peut plus la tester sans en lever une, ni changer de mode de stockage sans rouvrir le domaine.

Les deux autres descendent bien d'un cran. Le 3 mérite un mot : un contrôleur d'application qui parle à la persistance est parfaitement normal — c'est même son travail, aller chercher les entités et les remettre. Ce que la règle interdit, c'est que le métier le fasse.

Relire son diagramme avant de le rendre

Tout ce qui précède se perd si le diagramme rendu oublie ses visibilités. Cette dernière section n'apporte donc aucune notion neuve : elle donne la relecture à faire, dans l'ordre, avant de remettre une copie ou un livrable. Elle prend trois minutes et elle rapporte plus que n'importe quel raffinement de conception.

  1. Chaque boîte a-t-elle ses trois compartiments ? Et le troisième est-il rempli ? Une classe sans opération, dans un diagramme de conception, est soit une erreur, soit un aveu — on n'a pas encore décidé ce qu'elle fait.
  2. Chaque attribut et chaque opération porte-t-il sa visibilité ? C'est le premier point que le correcteur balaie, parce qu'il se vérifie sans lire.
  3. Chaque attribut et chaque opération porte-t-il son type ? Retour compris — : void s'écrit.
  4. Chaque association porte-t-elle ses deux multiplicités ? Le module Le modèle du domaine l'appelait déjà l'erreur la plus fréquente du cours ; elle ne disparaît pas en conception, elle s'y ajoute la navigabilité.
  5. Chaque flèche correspond-elle à un besoin que tu peux nommer ? Et les bidirectionnelles, les as-tu vraiment voulues ?
  6. Chaque classe est-elle dans une couche, et chaque dépendance descend-elle ? Une seule flèche qui remonte suffit à casser l'architecture qu'on annonce.
  7. Les noms viennent-ils du modèle du domaine ? Si une entité s'est fait renommer en chemin, remets son nom d'origine.

Une dernière chose, qui n'est pas de la relecture mais qui vaut d'être dite ici. Un diagramme de conception n'a pas à être complet pour être bon. Montrer trois classes et leurs vraies relations vaut mieux que trente boîtes dont on n'a rempli aucun troisième compartiment. Ce qu'on évalue est la cohérence des décisions, pas le nombre de rectangles.

🎓 À l'examen

Cette matière est celle de la semaine 4, é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. Et c'est la matière du Livrable 2 — Modèle de conception et Architecture logique, à remettre le 13 octobre (10 %) : le diagramme de classes et l'architecture logique en sont les deux moitiés nommées. Chez Larman, ce sont les chapitres 12, 13, 16, 34 et 35.

IFT-2007 n'autorise aucun matériel. La grammaire d'une ligne, les quatre symboles de visibilité, la différence entre trait plein et trait pointillé et l'ordre des quatre couches doivent être en tête — ce sont quatre choses qu'on peut rater alors même qu'on a compris toute la leçon.

« Transformez ce modèle du domaine en diagramme de classes de conception. »

L'énoncé classique, et il se traite dans un ordre qui rapporte des points même quand le résultat diffère du corrigé. Reprends les entités du domaine avec leurs associations ; ajoute à chaque attribut son type et sa visibilité — privée par défaut ; ajoute les opérations que chaque entité peut porter seule ; ajoute un contrôleur qui reçoit les opérations système ; puis tranche la navigabilité de chaque association.

Ce qui distingue une bonne copie : elle justifie deux ou trois choix en une ligne — pourquoi cette flèche va dans ce sens, pourquoi cette opération est sur cette classe. Un diagramme de conception se défend ; le correcteur cherche la trace de la décision.

« Quelle est la différence entre une association et une dépendance ? »

La durée du lien, et c'est la réponse en une phrase : une association donne un attribut qui vit aussi longtemps que l'objet, une dépendance ne dure que le temps d'un appel. Puis la notation : trait plein contre trait pointillé, et une dépendance ne porte pas de multiplicités.

Ajoute la conséquence, qui montre que tu as compris plutôt que récité : la dépendance est le lien le plus faible avec lequel deux classes peuvent encore collaborer, donc celui qu'on préfère chaque fois qu'il suffit.

« Décrivez l'architecture en couches et justifiez sa règle de dépendance. »

Nomme les quatre couches dans l'ordre — présentation, application, métier, persistance —, donne un exemple de classe pour chacune, et énonce la règle : une couche n'appelle que celle immédiatement en dessous, jamais au-dessus, jamais en sautant.

Pour la justification, évite la liste de mots (découplage, testabilité…) que tout le monde écrira. Dis le mécanisme : appeler, c'est dépendre, donc le sens des flèches est le sens dans lequel un changement se propage — et on fait pointer les couches instables vers les couches stables, jamais l'inverse. Le test de l'interface remplaçable par une ligne de commande vaut un point à lui seul.

À retenir

Le diagramme de classes de conception emploie la notation du modèle du domaine pour dire tout autre chose : non plus ce que le métier est, mais ce que le logiciel fera. Ce qu'il ajoute — les types, la visibilité, les opérations, les classes techniques — n'est jamais une simple précision : chaque ligne est une décision qu'on aurait pu prendre autrement, et c'est pour ça qu'un tel diagramme ne se fait pas valider par le client, il se défend devant l'équipe.

Deux traits neufs font tout le travail. La navigabilité décide qui garde la référence de qui, et se traduit littéralement en champs : une flèche, un attribut ; une extrémité à plusieurs, une collection. La dépendance, en pointillé, dit qu'on se sert d'une classe sans la garder — c'est le lien le plus faible avec lequel deux classes collaborent encore, donc celui qu'on préfère. Au-dessus des classes, l'architecture logique range le tout en paquetages et en couches, et une seule règle les tient : on n'appelle que la couche du dessous, parce qu'appeler, c'est dépendre.

Et ailleurs : le raisonnement des couches n'a rien de propre à UML ni à Java. C'est la même question partout — qui a le droit de connaître qui ? — et elle décide de la santé d'un projet bien plus sûrement que le choix d'un langage. Tu la retrouveras dans un découpage en modules, dans les frontières d'un service web, dans un projet frontend où l'accès aux données remonte jusque dans les composants d'affichage. Savoir tracer une flèche dans le bon sens, c'est savoir quelle partie d'un système pourra changer sans emporter le reste.

🗂️ L'aide-mémoire
Les quatre symboles de visibilité
+ public · - privé · # protégé (la classe et ses sous-classes) · ~ paquetage
La forme d'une ligne d'attribut
visibilité nom : Type [multiplicité] = valeurParDefaut — seuls le nom et la visibilité sont obligatoires
La forme d'une ligne d'opération
visibilité nom(param : Type, ...) : TypeRetour — le type de retour s'écrit toujours, void compris
Ce que veulent dire le soulignement et l'italique
souligné = membre de classe (static) · italique = abstrait, qu'il s'agisse d'une classe ou d'une opération
Ce que devient une flèche de navigabilité dans le code
un attribut du type visé. Multiplicité 1 → un champ ; 0..* ou 1..* → une collection
Ce qui distingue un trait plein d'un trait pointillé
la durée : l'association garde un attribut, la dépendance ne dure qu'un appel — paramètre, retour, variable locale. Une dépendance ne porte pas de multiplicités
Où atterrit une opération système
sur un contrôleur, un par cas d'utilisation. C'est la seule porte d'entrée, comme le DSS n'avait qu'un seul rectangle
Les trois stéréotypes, et ce que chacun touche
«entity» le domaine · «boundary» le monde extérieur (écran, appareil, autre système) · «controller» l'orchestration
Les quatre couches, dans l'ordre
présentation, application, métier, persistance — et une couche n'appelle que celle immédiatement en dessous
Ce qu'un paquetage vaut en Java
un dossier, plus une ligne package. Sans mot-clé devant un membre, la visibilité est celle du paquetage
Le premier point que le correcteur vérifie
la visibilité de chaque attribut et de chaque opération : elle se contrôle sans rien lire, et son absence se répète sur toute la copie