Aller au contenu principal
genie-logicielModéliser avec UML

Modéliser avec UML

Ce que ce chapitre apporte

  • Rappeler les quatre piliers de la programmation orientée objet.
  • Lire et produire un diagramme de classes avec ses relations et ses cardinalités.
  • Distinguer association, agrégation, composition et héritage.
  • Lire et produire un diagramme de séquence.
  • Reconnaître les principaux types de diagrammes UML et leur usage.
  • Détecter les problèmes de conception qu'un diagramme rend visibles.
Où on va
Discuter d'une conception à l'oral produit autant de représentations mentales que d'interlocuteurs. UML fournit une notation commune pour dessiner la structure d'un logiciel et le déroulement de ses échanges. Ce chapitre s'en tient aux deux diagrammes réellement employés en conception objet, le diagramme de classes et le diagramme de séquence, après un rappel des notions de programmation objet qu'ils servent à représenter.

Les quatre piliers de l'objet

Définitions

L'encapsulation regroupe les données et les traitements qui les concernent, et masque l'état interne derrière une interface.

L'abstraction expose ce qu'un objet fait sans dire comment il le fait.

L'héritage permet à une classe de reprendre le comportement d'une autre en le spécialisant.

Le polymorphisme permet de traiter des objets de types différents à travers une même interface, chacun réagissant à sa manière.

Ces notions ne sont pas quatre fonctionnalités du langage mais quatre façons de réduire le couplage. L'encapsulation empêche le reste du programme de dépendre d'un détail interne. L'abstraction permet de remplacer une implémentation. Le polymorphisme évite les enchaînements de tests sur le type.

Le signe qu'il manque du polymorphisme
Une suite de if ou un switch qui teste le type d'un objet pour décider du traitement à appliquer signale presque toujours qu'un comportement aurait dû être délégué à l'objet lui-même.
Le test devra être complété à chaque nouveau type, dans tous les endroits où il apparaît. Une méthode redéfinie dans chaque sous-classe, elle, n'oblige à toucher qu'un seul fichier.

Le diagramme de classes

C'est le diagramme le plus employé. Il décrit la structure statique : quelles classes existent, ce qu'elles contiennent, et comment elles sont reliées.

        +---------------------------+
        |         Fichier           |
        +---------------------------+
        | - chemin : string         |    - prive
        | - taille : long           |    + public
        | # verifie : bool          |    # protege
        +---------------------------+
        | + Lire() : byte[]         |
        | + Taille() : long         |
        +---------------------------+

Un compartiment pour le nom, un pour les attributs, un pour les opérations. Les symboles de visibilité précèdent chaque membre : + public, - privé, # protégé.

Les relations

RelationNotationSens
Associationtrait simpledeux classes se connaissent
Agrégationlosange creuxl'une contient l'autre, qui lui survit
Compositionlosange pleinl'une contient l'autre, qui disparaît avec elle
Héritageflèche triangulaire creusel'une est une sorte de l'autre
Dépendanceflèche pointilléel'une utilise l'autre ponctuellement
Agrégation ou composition, le test qui tranche
Demande-toi ce qui arrive à la partie quand le tout disparaît.
Une commande contient des lignes de commande : supprimer la commande supprime ses lignes, qui n'ont aucune existence indépendante. C'est une composition.
Une équipe contient des employés : dissoudre l'équipe ne supprime pas les employés, qui existaient avant et continueront après. C'est une agrégation.

Les cardinalités précisent combien d'objets participent à chaque extrémité : 1, 0..1, 1..*, *. Elles se lisent en partant de la classe opposée, ce qui est la source d'erreur la plus fréquente.

  Sauvegarde  1 ------------- 0..*  Fichier
              une sauvegarde porte sur zero ou plusieurs fichiers
              un fichier appartient a exactement une sauvegarde

Héritage et hiérarchies

L'héritage se lit toujours dans le sens « est une sorte de ». Si la phrase ne tient pas, la relation est probablement une composition.

main.py
Sortie
>_ Prêt à exécuter…
Hériter pour réutiliser du code
Faire hériter une classe d'une autre parce qu'elle en réutilise trois méthodes crée une dépendance permanente sur toute la classe parente, y compris sur ce qui ne concerne pas la fille.
La question à poser reste « est-ce une sorte de » et non « ai-je besoin de ces méthodes ». Quand la réponse est la seconde, la composition convient mieux : la classe détient une instance de l'autre et lui délègue ce qu'elle en attend.

Le diagramme de séquence

Là où le diagramme de classes montre la structure, le diagramme de séquence montre le déroulement : quels objets échangent quels messages, dans quel ordre.

 :Interface        :Gestionnaire       :Fichier        :Journal
     |                   |                 |               |
     |-- Lancer() ------>|                 |               |
     |                   |-- Taille() ---->|               |
     |                   |<-- 4096 --------|               |
     |                   |-- Copier() ---->|               |
     |                   |<-- ok ----------|               |
     |                   |-- Ecrire(trace) --------------->|
     |<-- termine -------|                 |               |

Le temps s'écoule vers le bas. Chaque ligne de vie verticale représente un objet, et les rectangles superposés marquent les périodes pendant lesquelles il est actif. Une flèche pleine est un appel, une flèche pointillée un retour.

Ce que le diagramme de séquence rend visible
Il montre les dépendances réelles à l'exécution, que le diagramme de classes ne dit pas. Si l'interface appelle directement le journal, alors qu'elle ne devrait connaître que le gestionnaire, cela se voit immédiatement : une flèche traverse le schéma.
Il montre aussi les allers-retours excessifs. Vingt messages entre deux objets pour une opération simple signalent un découpage à revoir.

Les autres diagrammes

UML en définit une quinzaine, répartis en deux familles. En conception objet courante, quatre suffisent.

DiagrammeFamilleCe qu'il décrit
Classesstructureles types et leurs relations
Séquencecomportementl'ordre des échanges entre objets
Cas d'utilisationcomportementce que le système offre, vu de l'extérieur
Étatscomportementles états d'un objet et les transitions
Composants, déploiementstructurel'organisation physique du logiciel
Modéliser n'est pas documenter
Un diagramme qui reproduit fidèlement le code n'apprend rien de plus que le code, et devient faux dès la première modification. Il coûte à produire et à maintenir sans rien apporter.
Un diagramme sert à décider avant d'écrire, ou à expliquer une partie difficile à quelqu'un. Hors de ces deux usages, il vaut mieux ne pas le faire.

Exercices type

Association, agrégation ou composition entre une facture et ses lignes ?

Composition. Une ligne de facture n'a pas d'existence hors de sa facture : elle est créée avec elle et disparaît avec elle.

Le test est celui du cycle de vie. Si supprimer le tout doit supprimer la partie, c'est une composition, notée par un losange plein du côté du tout.

Contre-exemple dans le même domaine : la relation entre une facture et un client est une association simple. Supprimer la facture ne supprime pas le client, et le client existait avant.

Comment lire la cardinalité Auteur 1 ---- 0..* Livre ?

En partant de chaque extrémité vers l'opposée.

Côté auteur : un auteur a écrit zéro ou plusieurs livres.

Côté livre : un livre a exactement un auteur.

La cardinalité inscrite près d'une classe décrit combien d'instances de cette classe participent à la relation, pour une instance de l'autre. C'est le sens inverse de la lecture spontanée, ce qui explique la fréquence de l'erreur.

Le modèle proposé ici interdit d'ailleurs les livres coécrits, ce qui est peut-être une contrainte à discuter avec le client.

Pourquoi C# interdit-il l'héritage multiple de classes ?

À cause de l'ambiguïté que produit le losange. Si une classe hérite de deux classes qui héritent elles-mêmes d'une troisième, elle atteint cet ancêtre par deux chemins.

Deux questions n'ont alors pas de réponse évidente : quelle version d'une méthode redéfinie des deux côtés faut-il appeler, et l'état de l'ancêtre existe-t-il en un ou deux exemplaires.

C# autorise en revanche l'héritage multiple d'interfaces, qui ne portent pas d'état. L'ambiguïté sur les données disparaît, et celle sur les méthodes se règle par une implémentation explicite.

Que révèle une classe avec vingt attributs et trente méthodes ?

Une cohésion faible. Une classe de cette taille assume presque certainement plusieurs responsabilités.

Le diagnostic se fait en cherchant des groupes : quels attributs sont utilisés par quelles méthodes. Si deux sous-ensembles apparaissent, sans recouvrement, la classe en contient deux.

La conséquence pratique est un fort couplage. Une classe qui fait tout est utilisée par tout le monde, et chacune de ses évolutions risque de casser un appelant.

Le découpage se fait par responsabilité, en reprenant la règle du premier chapitre : une classe, une raison de changer.

Quand un diagramme de séquence apporte-t-il plus qu'un diagramme de classes ?

Quand la difficulté porte sur l'ordre des opérations plutôt que sur la structure.

Un protocole d'échange, une séquence d'initialisation avec des dépendances, un traitement où plusieurs objets se répondent : le diagramme de classes montre qui connaît qui, mais pas dans quel ordre les appels se produisent.

Il est aussi le seul à rendre visibles les dépendances qui contournent l'architecture prévue, comme un objet de présentation qui appellerait directement la couche de données.

Faut-il maintenir les diagrammes à jour tout au long du projet ?

Cela dépend de leur usage, et la réponse par défaut est non.

Un diagramme produit pour décider d'une conception a rempli son rôle une fois la décision prise. Le maintenir revient à documenter le code deux fois, en sachant que la seconde version sera fausse.

Un diagramme d'architecture générale, qui montre les grandes couches et leurs relations, mérite d'être tenu à jour : il change rarement, et il sert à chaque arrivée dans l'équipe.

La règle utile porte sur le niveau de détail. Plus un diagramme est proche du code, plus il se périme vite et moins il apporte.

La méthode

  1. Choisis le diagramme selon la question : structure, ordre des échanges, ou usage extérieur.
  2. Vérifie chaque héritage avec la phrase « est une sorte de ».
  3. Détermine agrégation ou composition par le cycle de vie de la partie.
  4. Lis les cardinalités depuis la classe opposée.
  5. Cherche les classes trop grosses : elles signalent une cohésion faible.
  6. Regarde les flèches qui traversent un diagramme de séquence, elles trahissent les dépendances non prévues.
  7. Ne maintiens que les diagrammes qui servent encore.

En résumé

  • Encapsulation, abstraction, héritage, polymorphisme réduisent tous le couplage.
  • Un switch sur le type d'un objet signale un polymorphisme manquant.
  • Le diagramme de classes décrit la structure : attributs, opérations, visibilité, relations.
  • Agrégation : la partie survit au tout. Composition : elle disparaît avec lui.
  • Les cardinalités se lisent depuis la classe opposée.
  • L'héritage multiple de classes crée le problème du losange ; C# ne l'autorise que pour les interfaces.
  • Le diagramme de séquence montre l'ordre des échanges et rend visibles les dépendances imprévues.
  • Un diagramme sert à décider ou à expliquer. S'il ne fait que redire le code, il coûte plus qu'il n'apporte.

Et ensuite ? Certains problèmes de conception reviennent si souvent qu'ils ont reçu des solutions éprouvées et nommées. Le chapitre suivant présente les design patterns.

Modéliser avec UML | Plateforme ETS