Keyboard shortcuts

Touchez ← ou → pour naviguer les chapitres

Touchez S ou / pour chercher dans le livre

Touchez ? pour afficher ce message

Touchez Esc pour masquer ce message

Accueil > Programmation orientée objet >

📚 Composition

Survol et attentes

Quand vous avez un peu d’expĂ©rience avec l’hĂ©ritage, le polymorphisme, les classes abstraites et les interfaces, vous aurez probablement envie de dĂ©velopper des projets plus complexes. La composition est un concept important que vous devrez comprendre afin de planifier les relations entres les classes pour les rendre facile Ă  utiliser et Ă  modifier.

C’est un sujet avancĂ© en programmation orientĂ©e-objet. Cette leçon est donc optionnelle. Elle est destinĂ©e aux Ă©tudiants qui veulent aller plus loin dans leur apprentissage de la programmation orientĂ©e-objet. Elle n’est pas nĂ©cessaire pour la rĂ©ussite du cours ICS4U.

Définitions

La composition est une alternative Ă  l’hĂ©ritage pour la rĂ©utilisation de comportements communs. Au lieu d’étendre ou d’implĂ©menter une classe ou une interface, une classe applique la composition en dĂ©clarant des attributs (variables) du type de la classe ou de l’interface qu’elle veut rĂ©utiliser. Cette classe “se compose” alors en intĂ©grant ces attributs spĂ©cifiques (et les mĂ©thodes qu’elles dĂ©finissent).

La composition se fait gĂ©nĂ©ralement avec des interfaces parce que chaque interface est gĂ©nĂ©ralement trĂšs spĂ©cifique. Et on peut composer quelque chose avec peu ou avec beaucoup de composants, soit selon les besoins de l’objet.

Pourquoi pas simplement implĂ©menter directement toutes ces interfaces? Dans l’implĂ©mentation, la classe dĂ©finit elle-mĂȘme l’implĂ©mentation de chaque interface. Elle fixe alors son comportement. Dans la composition, la classe dĂ©lĂšgue l’implĂ©mentation Ă  l’objet qu’elle contient. C’est-Ă -dire que son comportement peut changer selon l’objet qu’elle assigne Ă  son attribut.

Avec l’hĂ©ritage et l’implmentation directe

Voici un exemple de code pour un personnage de jeu vidéo qui peut se déplacer et attaquer. Il implémente directement les interfaces Moves et Skill.

public interface Moves {
    void move();
}
public interface Skill {
    void applySkill();
}
public class Warrior implements Moves, Skill {
    private int health;
    private int skillLevel;
    // ...autres attributs, constructeurs, etc.

    @Override
    void move(){
        System.out.println("Marching and yelling");
    }

    @Override
    void applySkill(){
        System.out.println("Fighting with sword");
    }
}
public class Wizard implements Moves, Skill {
    private int health;
    private int skillLevel;
    // ...autres attributs

    @Override
    void move(){
        System.out.println("Floating on an energy ball");
    }

    @Override
    void applySkill(){
        System.out.println("Lightning bolt from a staff");
    }
}

basic players

Tout ça est acceptable Ă  la base, mais qu’est-ce qui se passe si le Warrior se bat avec une arme diffĂ©rente ou se dĂ©place Ă  cheval? Ou si le Wizard change Ă©galement ses comportements durant le jeu. Il faudrait crĂ©er des classes pour chaque variante possible d’un Warrior et d’un Wizard, p.ex : MountedWarriorWithSpear, MountedWarriorWithSword, etc. C’est beaucoup de classes Ă  crĂ©er et Ă  maintenir! Et si ces personnages devaient Ă©ventuellement incorporer d’autres comportements, il faudrait encore plus de classes!!!

Avec la composition

La composition nous offre une autre solution. Au lieu d’implĂ©menter directement les interfaces dans les classes des diffĂ©rents types de personnages, on peut utiliser les interfaces comme attributs dans une classe parente. Si on implĂ©mente les interfaces dans des petites classes Ă  part, les diffĂ©rents types de personnages ont simplement Ă  s’assigner une des ces classes comme valeur de leur attribut. Et si dans le futur on ajoute de nouveaux comportments pour les interfaces existantes, rien ne change dans le code du personnage. En fait, mĂȘme s’il y a aucun nouveau code mais le personnage Ă©volue durant le jeu et obtient un nouveau comportement, c’est aussi simple que de lui assigner la bonne implĂ©mentation : on peut changer les comportements durant l’exĂ©cution du programme! Finalement, si on ajoute un nouveau comportement dans une nouvelle version du programme, on ajoute juste un nouvel attribut et les mĂ©thodes nĂ©cessaires pour l’utiliser (accesseurs, mutateurs, etc.). Le code existant n’est pas affectĂ©.

Voici Ă  quoi ça ressemble, prĂ©sumant que les interfaces Moves et Skill sont inchangĂ©es de l’exemple prĂ©cĂ©dent :

public class MarchingAndYelling implements Moves {
    @Override
    void move(){
        System.out.println("Marching and yelling");
    }
}
public class FightingWithSword implements Skill {
    @Override
    void applySkill(){
        System.out.println("Fighting with sword");
    }
}

..etc. Il y a plusieurs petites classes comme celles-ci, chacune implémentant une interface de façon spécifique.

public abstract class Player {
    private int health;
    private int skillLevel;
    private Moves move; // voici la composition : inclut l'interface comme attribut
    private Skill skill;
    // ...autres attributs, constructeurs, etc.

    public void move(){ // parce que c'est une interface on peut définir l'utilisation publique ici
        move.move();
    }
    public void applySkill(){
        skill.applySkill();
    }
}
public class Warrior extends Player{
    public Warrior(){
        super();
        this.move = new MarchingAndYelling(); // l'enfant choisit l'implémentation spécifique
        this.skill = new FightingWithSword();
    }
}
public class Wizard extends Player{
    public Wizard(){
        super();
        this.move = new FloatingOnEnergyBall();
        this.skill = new LightningBoltFromStaff();
    }
}

strategy

Dans le cas de la composition, une classe parente abstraite dĂ©finit la structure d’un personnage. Cette structure inclut des attributs qui sont des interfaces (on le reprĂ©sente dans un diagramme UML avec un diamant sur la classe qui “se compose de” l’autre classe). Les constructeurs des classes enfants dĂ©finissent les valeurs spĂ©cifiques Ă  insĂ©rer dans la structure.

L’architecture prĂ©sentĂ©e dans cet exemple avec les personnages et leur comportements s’appelle le patron de conception Strategy. Il y a plusieurs autres patrons de conception qui offrent des solutions pratiques Ă  des problĂšmes courants en dĂ©veloppement logiciel, mais cela dĂ©passe le cadre de ce cours.

En utilisant une classe parente abstraite avec la composition, on applique plusieurs bonnes pratiques en programmation orientée-objet :

  • La composition produit un couplage faible entre les classes. Les classes sont indĂ©pendantes les unes des autres. On peut changer une classe sans affecter les autres ou sans exiger la production d’une quantitĂ© Ă©norme de code pour acceuillir le changement.
  • La composition rĂ©duit aussi le nombre de fois qu’il faut supplanter les mĂ©thodes abstraites, notamment le nombre de fois qu’on Ă©crit une implĂ©mentation qui sera immĂ©diatement supplantĂ©e par la classe enfant spĂ©cifique que le programme utilise.
  • L’hĂ©ritage des attributs d’une classe parente Ă©vite d’avoir Ă  copier la structure de la classe pour chaque type. C’est le principe DRY : “don’t repeat yourself”. Et si on voulait augmenter la capacitĂ© de Player, en y ajoutant un nouvel attribut par exemple, on n’aurait qu’à le faire dans la classe parente. Les classes enfants n’auraient qu’à spĂ©cifier la partie qui leur est unique : le choix d’une implĂ©mentation pour le nouvel attribut.

Objectifs d’apprentissage

À la fin de cette leçon vous devrez ĂȘtre en mesure de :

  • Explorer des architectures plus complexes qui appliquent la composition afin de voir comment utiliser la programmation orientĂ©e-objet dans un projet de dĂ©veloppement logiciel plus rĂ©aliste.

CritĂšres de succĂšs

  • Je peux incorporer la composition dans mes propres classes.

Exercices

Recherche :

  • Faire une recherche sur un patron de conception qui vous intĂ©resse.
    • Tentez d’identifier les concepts de la programmation orientĂ©e-objet qui sont appliquĂ©es : hĂ©ritage, interfaces, composition, etc.
    • Identifier des cas oĂč ce patron de conception pourrait ĂȘtre utile dans un projet de dĂ©veloppement logiciel.

© 2022-2025 David Crowley