Aperçu des sections

  • Présentation

    Note importante : pour ceux qui ont déjà vu la méthode Merise, et qui maîtrisent déjà un minimum la POO, vous partez avec une longueur d'avance. Vous n'aurez aucun mal à vous retrouver dans les diagrammes de classe. La principale différence avec la méthode Merise est que l'on part sur une base "objet", avec toutes les notions que cela implique (méthodes, attributs, héritage, etc...). Si vous en ressentez le besoin, vous pouvez vous référer aux cours sur la conception de BDD (selon Merise) et la Programmation Orientée Objet. Pour l'UML, nous partirons du fait que certaines notions sont acquises, quitte à y revenir dessus si nécessaire.


    Le diagramme de classe permet de schématiser l'ensemble des classes et de leurs relations présentent dans une application. 

    Il permet de déterminer les classes qu'un logiciel besoin pour fonctionner, mais aussi les classes association , déduites des relations entre les classes .

    Le diagramme de classe se base sur un ensemble de conventions schématiques afin de normaliser sa réalisation.


    Exemple de diagramme de classe (avec annotations) pour une application de commande de produits :


    • Classes

      Le cœur des diagrammes de classe, c'est la notion POO de "classe". Voir le cours la programmation orientée objet si vous ne maîtrisez la notion de classe.

      Attention, l'UML différencie la notion de classe et d'instance (objet), car une classe peut très bien ne jamais être instanciée (classe statique), ce qui est le cas dans de nombreuses architectures.

      Les classes correspondent aux entités de base de votre application. De nouvelles classes, non prévues, peuvent émerger au fil de la conception, dû aux multiplicités, ce sont les classes association.

      En UML, une classe est déterminée par les notions habituelles :

      • son nom
      • ses attributs
      • ses opérations (futures méthodes)
      • la visibilité des attributs et opérations
      • l'aspect statique, ou non-statique, des attributs et opérations


      Conventions

      Pensez à respecter les styles d'écritures habituels :

      • PascalCase pour le nom des classes
      • camelCase pour le nom des attributs et opérations


      De plus, choisissez des noms qui ont du sens, et en anglais. Cela est très important pour la compréhension de votre travail par une tierce personne, et le respect de l'anglais comme langue commune et "facilitante" (pas de problème d'accent ou de caractères spéciaux par exemple).

      Signe pour les différentes visibilités :

      • -  : privé
      • + : public
      • # : protégé
      • ~ : limité au package (moins répandu)

      Afin de respecter le principe d'encapsulation, les attributs sont tous censés être privés. Ils peuvent devenir publiques dans certains cas, uniquement si cela est justifié.

      Les attributs ou méthodes statiques seront simplement soulignés.

      Optionnel : on pourra aussi représenter les types de données, les paramètres (et leur type), les procédures et le type de retour des méthodes. De plus, vous pouvez réserver un dernier encart qui peut expliquer brièvement le rôle de la classe.


      Voici quelques exemples de représentation de classes seule en UML :


      Il y a une petite erreur dans celle-ci, trouvez laquelle :


      Traduire le type de l'attribut "status" dans le MPD :


      • Associations

        Une fois que vos principales classes ont été "posées" sur votre diagramme, il va falloir établir les liens qui existent entre elles.


        Association bidirectionnelle

        Les associations bidirectionnelles sont représentées par un simple trait entre deux classes. 

        On peut nommer une association afin de rendre la relation plus claire (souvent avec un verbe à l'infinitif)

        Il peut exister plusieurs associations entre deux classes. Dans ce cas le nommage de l'association est obligatoire afin de clarifier le rôle des diverses associations.

        Les associations bidirectionnelles ne permettent pas de déterminer la navigabilité entre les objets de 2 classes reliés.


        Association simple


        Association multiple


        Association réflexive

        Autres exemples : menu avec sous catégorie, arborescence de système de fichier, etc...




        Association uni-directionnelle

        Les associations uni-directionnelles permettent de schématiser la navigabilité entre les classes.

        Si l'on souhaite schématiser le fait que les objets d'une classe A ont accès aux objets d'une classe B, mais non l'inverse, on utilisera alors une association uni-directionnelle.

        Exemple : association uni-directionnelle vs. bidirectionnelle




        Association n-aire

        Utilisée moins fréquemment, car complexifie la conception par la suite. Le nœud de l'association n-aire est alors représenté par un losange.


        Association interdite

        On peut aussi schématiser que toute association entre deux classes est interdite :



        Récapitulatif sur les associations



        • Multiplicités

          Les multiplicités sont l'équivalent des cardinalités du modèle Merise.

          Les multiplicités se positionnent à l'inverse du modèle Merise, non pas à côté de la classe de départ, mais à côté de la classe d'arrivée.


          Elles ne s'appliquent que lorsqu'une classe est instanciable, donc non-statique. Les multiplicités reflètent le nombre d'objets instanciables entre 2 classes, donc une classe statique n'est pas concernée.

          Elles permettent de représenter le nombre d'objets possibles entre deux classes reliées l'une à l'autre.

          Les multiplicités se divisent en trois grandes catégories d'associations :

          • les one to one (un à un)
          • les one to many (un à plusieurs)
          • les many to many (plusieurs à plusieurs)

          Exemples :

          One to one

          Un match ne peut se passer que sur un seul terrain. Association one to one de la classe Match vers la classe Terrain. On peut déterminer l'association en disant : "Pour un match, il y a au minimum un terrain, et au maximum un terrain".

          One to many

          Un match doit être arbitrer par au moins un arbitre, mais il peut y avoir d'autres arbitres (arbitres de touche par exemple). Association one to many de la classe Match vers la classe Arbitre. On peut déterminer l'association en disant : "Pour un match, il y a au minimum un arbitre, et au maximum plusieurs arbitres".

          Many to many

          Une équipe est composée de plusieurs joueurs (entre 11 et 18 par exemple). Association many to many de la classe Equipe vers la classe Joueur. On peut déterminer l'association en disant : "Pour une équipe, il y a au minimum plusieurs joueurs, et au maximum plusieurs joueurs".


          Conventions

          On représentera ces multiplicités sur le diagramme en notant le plus petit chiffre à gauche, et le plus grand à droite, séparés par deux points ... (ex : 0..1)

          Le cas "au minimum zéro" est possible (ex : pour une Equipe, il n'y a pas encore de Match de prévu)

          Le cas "plusieurs" (avec un maximum non déterminé), se représente avec le signe *

          On peut aussi déterminer les nombre exact lorsqu’on les connait (ex : un match doit contenir pile 2 équipes), mais cela ne pourra se traduire que dans le code, et non dans la structure de la DB. "2" sera alors considéré comme "many" (supérieur à 1).


          Exemples :

          • one to one : 1 (raccourci pour 1..1)
          • one to many : 1..*
          • many to many : * (raccourci pour *..*)
          • zero to many : 0..*


          Exemples de modélisation de notre appli de foot :



          • Rôles

            Nomme l'extrémité d'une association

            Permet d'accéder aux objets liés, par l'association à un objet donné.

            Permettra de déterminer le nom des méthodes stockant les objets d'une classe B dans une classe A (si la flèche va de A vers B).

            Dans le cas d'association uni directionnelles, le rôle ne se mettra que dans la classe navigable (match / arbitre).

            Si association bidirectionnelle, alors prévoir des rôles dans les deux sens (equipe / joueurs).


            On pourra facilement déduire de ce schéma les futures méthodes d'accès aux objets dans toutes les classes. Par exemple pour la classe Match : getArbitres(), getTerrain() et getEquipes().
            Note : le type de l'attribut rôle de la classe Joueur est une énumération. Concrètement, cela peut être mis en place soit en DB (via un type ENUM spécial), soit via une classe statique supplémentaire.
            • Classes association

              Parfois, on a besoin de rajouter des propriétés à une association.

              Cela arrive lorsque l'on souhaite décrire quelque chose qui n'appartient ni à une classe A, ni a une classe B, mais bien à l'association entre ces deux classes.

              Dans ce cas, on va créer une nouvelle classe, appelée classe association.

              Elle permettra de décrire les attributs ou opérations liés à l'association de ces 2 classes.

              Exemples et notation de classes association :



              Exemple avec notre appli de foot :

              Rajoutons la notion de Sponsor dans notre application de foot. Admettons que chaque Equipe peut avoir des sponsors, et inversement, chaque sponsor peut financer des équipes. 

              Dans ce cas, nous avons une relation many to many entre la classe Equipe et la classe Sponsor.


              Voici la relation de base :


              Maintenant, nous voulons rajouter trois attributs dans notre conception :

              1. date de début d'un financement
              2. date de fin d'un financement
              3. montant d'un financement

              Ces trois attributs ne sont ni reliés à l'équipe (qui peut être financée par plusieurs sponsors), ni reliés au sponsor (qui peut financer plusieurs équipes).

              Dans ce cas de figure, vous devez créer une classe association.

              Exemple :



              Notez bien que la notion de Financement ne nous apparaissait pas de prime abord. Et pourtant, un sponsor peut très bien ne pas financer une équipe et une équipe peut très bien ne pas être financée par tel sponsor... D'où la création de la classe Financement

              C'est là tout l'intérêt d'une réflexion en amont, afin de détecter les éléments subtils, mais important, de votre application.


              Équivalence en termes de classe association :


              • Hiérarchie de classe

                Grâce à certains concepts de POO, on va pouvoir hiérarchiser nos différentes classes entre elles.

                On se retrouvera avec des classes de haut niveau (classes "parentes"), et des sous-classes (classes "enfants").

                Cette hiérarchisation va permettre de regrouper des informations de manière plus fidèle à la réalité des besoins de notre application.

                La notion d'héritage est centrale dans cet aspect des diagrammes de classe.

                Voici un exemple de classes hiérarchisées en UML :



                Comment déterminer une hiérarchie ?

                En procédant par fusion (ou fission) d'éléments communs à plusieurs classes.

                Spécialisation : on part d'une classe parente que l'on va découper en plusieurs classes enfant.

                Généralisation : on rassemble des éléments communs à plusieurs classes enfants vers une classe parente.


                Exemple avec des comptes bancaires :

                Tous les éléments en verts, communs aux classes CompteCommun et CompteEpargne, sont fusionnés dans la classe Compte



                • Généralisation

                  La généralisation se base sur le concept POO de l'héritage.

                  L'héritage simple permet de déterminer qu'une classe EST AUSSI une autre classe (ex : un chat EST UN animal, une voiture EST UN véhicule, etc...).

                  Une classe enfant hérite de tous les attributs et méthodes de la classe parente (qui ont une visibilité protected ou public).

                  L'héritage est un concept extrêmement utilisé, car il permet de regrouper des concepts généraux, partagés par plusieurs classes.

                  Il se détermine avec une simple flèche pleine en UML (contrairement aux associations "flèche simple")


                  Les classes héritées héritent aussi des associations de la classe parente.

                  Voici un exemple d'héritage simple avec notre appli de foot :


                  Tous les joueurs possèdent un id, un nom, et une date de naissance. En revanche, chaque type de joueur à ses propres spécialisations.


                  Exemple d'implémentation en PHP


                  class Animal
                  {

                  }
                  class Cat extends Animal
                  {
                  /* La classe Cat hérite de Animals, un chat EST un animal*/
                  }
                  class Dog extends Animal
                  {

                  }
                  • Abstraction

                    L'héritage "simple" sous-entend que l'on peut très bien instancier un objet de la classe parente (ici, Joueur). Ce qui veut dire que l'on a 4 types d'objets possibles en réalité : Attaquant, Défenseur, Gardien... mais aussi Joueur. On pourra donc avoir des joueurs qui ne sont ni attaquant, ni défenseur, ni gardien... Bizarre pour un match de foot quand même.

                    Si l'on souhaite interdire cette pratique, et faire en sorte qu'un joueur soit forcément une des classes enfant (ce qui parait logique ici), on devra alors rendre la classe parente abstraite.

                    Pour le notifier en UML, il suffit de mettre le nom de classe en italique, ou bien d'écrire "abstract" (entre guillemets) devant.

                    Pour résumer : une classe abstraite est une classe dont seuls les enfants peuvent être instanciées.



                    Exemple d'implémentation en PHP (7.4.3)

                    abstract class Joueur {
                    protected String $name;
                    function __construct($name) {
                    $this->name = $name;
                    }
                    abstract public function move(); /* Signature (méthode non implémentée) */
                    }

                    class Attaquant extends Joueur {

                    public function move() { /* implémentation */
                    echo "$this->name bouge vite<br>";
                    }
                    }
                    class Defenseur extends Joueur {
                    public function move() { /* implémentation */
                    echo "$this->name bouge lentement<br>";
                    }
                    }

                    /*Ok*/
                    $joueur1 = new Attaquant('ronaldo');
                    $joueur2 = new Defenseur('zidane');

                    $joueur1->move();
                    $joueur2->move();

                    /* Erreur, une classe abstraite ne peut pas être instanciée */
                    $joueur3 = new Joueur();

                    Ce code donnera ce résultat :


                    • Interfaces

                      Une interface est une spécification de comportement qu'une classe doit implémenter afin d'être utilisée par d'autres classes.

                      À la différence d'une classe abstraite, une interface ne dispose que d'attributs et de méthodes publiques, non implémentés. Cela veut dire que la méthode existe, mais qu'elle devra être écrite (spécifiée, implémentée), dans les classes qui implémente l'interface.

                      On dit qu'une méthode déclarée, mais non implémentée est une signature.

                      Une différence importante entre une classe abstraite et une interface, c'est qu'une classe ne peut hériter que d'une seule classe abstraite, alors qu'elle peut implémenter plusieurs interfaces. Cela peut s'avérer important dans la conception d'un système.

                      Mots clés :

                      • interface pour déclarer une interface
                      • implements pour spécifier qu'une classe utilise une interface


                      Exemple d'implémentation en PHP

                      Ici, les deux objets Arc et Fusil à pompe peuvent tirer, mais la façon dont ils le font sera totalement différente. Donc le code de la méthode shoot() devra être implémenté obligatoirement dans chaque classe.

                      interface Shootable {
                      public function shoot(); /*Signature*/
                      }
                      class Bow implements Shootable {
                      public function shoot() {
                      /* code de la méthode à écrire ici pour l'arc*/
                      }
                      }
                      class Shotgun implements Shootable {
                      public function shoot() {
                      /* code de la méthode à écrire ici pour le fusil à pompe*/
                      }
                      }
                      • Agrégation

                        Permet de définir une association particulière entre deux classes.

                        Engendre une dissymétrie entre les classes, une classe "prédomine" l'autre

                        On parle de relation composant-composite :

                        • le composite est le "conteneur"
                        • le composant est le "contenu"


                        Il y a deux types d'agrégation :

                        • agrégation faible (ou agrégation)
                        • composition



                        Note : voyez la conception comme quelque chose de souple, qui colle aux besoins fonctionnels, plutôt qu'à une réalité "absolue".

                        Exemple ci-dessus :

                        • logiciel de gestion de concession automobile (vente de voiture) : ok, chaque voiture a un moteur.
                        • logiciel de gestion d'une casse auto : pas ok, le moteur d'une voiture peut très bien avoir été démonté, et donc une voiture n'a pas forcément de moteur...


                        Contrairement à l'héritage, ou l'on peut dire qu'une sous-classe EST aussi une classe parente (un attaquant EST un joueur, un défenseur EST un joueur), le verbe à employer en agrégation serait plutôt AVOIR (une voiture A un moteur, une voiture PEUT AVOIR des passagers). C'est un bon moyen mnémotechnique.
                        • Agrégation faible

                          Note : par convenance, on utilise le terme "agrégation" lorsque l'on parle d'agrégation faible.

                          Des objets d'une classe A peuvent être contenus dans des objets d'une classe B.

                          On dit que le composite fait référence à ses composants.

                          L'agrégation est représentée par un losange blanc.


                          Exemple avec une playlist :

                          • une playlist peut contenir des chansons
                          • une chanson n'est pas forcément reliée à une playlist
                          • détruire une playlist ne détruit pas une chanson
                          • détruire une chanson ne détruit pas une playlist
                          • une chanson peut faire partie de plusieurs playlists



                          Exemple avec une équipe de foot :

                          • une équipe de foot est un ensemble de joueurs
                          • le nombre de joueurs à l'intérieur peut varier
                          • supprimer un joueur ne supprime pas une équipe
                          • supprimer une équipe ne supprime pas un joueur



                          Exemple d'implémentation en PHP


                          class SportsCarEngine {

                          }
                          class Car {
                          private $engine;
                          public function __construct($engine) {
                          $this->engine = $engine; /*Peut etre NULL, ou en avoir plusieurs*/
                          }
                          }
                          $sportsCarEngine = new SportsCarEngine();
                          $sportsCar = new Car($sportsCarEngine);

                          • Composition

                            Exprime une relation forte en deux classes.

                            Un composite contient forcément des composants. Une classe A contient des objets d'une classe B.

                            La destruction du composite entraine la destruction des composants.

                            Un composite ne fait partie que d'un seul composant à la fois.

                            La composition se schématise avec un losange noir.



                            Exemple avec immeuble (composite) et appartement (composant) :

                            • un immeuble doit contenir des appartements
                            • un appartement ne peut exister que dans un seul immeuble
                            • la destruction d'un immeuble entraine la destruction de tous ses appartements


                            Exemple avec l'appli de foot :

                            • ajoutons la notion d'équipement : crampons, maillots, protections.
                            • les Equipement sont obligatoires pour chaque Joueur.
                            • on peut alors dire qu'un joueur possède un équipement.
                            • admettons qu'une fois qu'un joueur possède un équipement, c'est le sien. Si le joueur part, il emporte son équipement avec lui.


                            Si un objet Joueur est détruit, ses objets référents Equipement le sont aussi (puisqu'il part avec). 

                            On peut aussi dire : si le composite est détruit, les composants le sont aussi.

                            Voici comment le schématiser en UML :


                            Exemple d'implémentation en PHP


                            Dans le cas de la composition, les trois objets Subject, Body et Receivers sont chargés dans le constructeur. 


                            class Subject {

                            }
                            class Body {

                            }
                            class Receivers {

                            }
                            class Email {
                            private $subject;
                            private $body;
                            private $receivers;
                            public function __construct() {
                            $this->subject = new Subject(); /*Instanciation d'un nouvel objet Subject dans la propriété subject*/
                            $this->body = new Body(); /*idem avec Body*/
                            $this->receivers = new Receivers(); /*idem avec receivers*/
                            }
                            }

                            • Logiciels et démo

                              Solution de facilité, mais peut s'avérer suffisant : 

                              • papier crayon

                              Solution en ligne : 

                              • draw.io

                              Client lourd Ubuntu friendly (testés) :

                              • BOUML (bien pour la génération de code)
                              • Umbrello
                              • Dia

                              Autres (non-testés) :

                              • papyrus (web ?)(linux ?)
                              • mysql workbench / oracle (client lourd)
                              • starUML
                              • etc...

                              Il existe généralement de nombreux plugins pour votre IDE préféré :

                              • Java (avec Eclipse)
                              • PHP (avec JetBrains ou phpStorm)
                              • plugins dans VSCode (plus ou moins utiles...)
                              • etc...

                              Listes sur le web :

                              Démo et génération de code :