• 4 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 30/10/2024

Manipulez les objets avec les design patterns de comportement

Découvrez les design patterns comportementaux

Si vous avez déjà patienté dans un grand aéroport, vous avez assisté au décollage et à l'atterrissage de nombreux avions. Comment cela se déroule-t-il chaque jour, (presque) sans encombre ? Une solution possible serait que chaque pilote soit en communication avec tous les autres pilotes afin de décider en commun de qui décolle et qui atterrit : une approche ingérable avec plus d'une demi-douzaine d'avions...

Il existe un pattern comportemental pour résoudre ce problème. On l'appelle la tour de contrôle : une place centrale responsable de la coordination de tous les autres objets. Au lieu de parler entre eux, les pilotes communiquent avec un contrôleur central. Le contrôleur envoie ensuite des instructions à tous les avions. Dans le monde du logiciel, ce pattern est appelé un médiateur.

Un lieu de contrôle centralisé
Une lieu de contrôle centralisé

Comme entre les avions, la communication est souvent complexe entre les objets. Les patterns comportementaux fournissent aux objets des moyens de communiquer entre eux de manière intelligente et systématique. 

Le pattern Observateur

Dans beaucoup de situations, vous allez disposer d'un objet portant des informations d'état dont les autres objets doivent avoir connaissance. Lorsque ces informations d'état changent, tous ces autres objets doivent en être informés. Vous utilisez souvent ce pattern, lorsque vous regardez la télévision ou que vous écoutez la radio !

Vous est-il déjà arrivé d'entrer dans une pièce, de voir la télévision, de vous dire « ça a l'air pas mal », et de commencer à regarder ? La télévision diffuse ses programmes, sans avoir besoin de modifier son mode de fonctionnement parce qu'une personne de plus est dans la pièce. Bon, souvent, vous finissez par vous lasser et vous partez. La télé, elle, ne fera rien de spécial après votre départ. Elle continuera à diffuser son programme dans la pièce.

C'est la même idée qui sous-tend le pattern Observateur. Les auditeurs intéressés peuvent aller et venir, mais l'objet exposant les informations ne change pas en fonction du nombre de spectateurs.

Imaginons qu'un grand nombre de personnes souhaitent assister, sans interagir, à notre jeu sur leur propre écran.

Puisque nous avons utilisé le pattern Composite dans le dernier chapitre, nous pouvons gérer une vue ou plusieurs de la même manière dans le contrôleur. Donc pas besoin de modifier le code dans le contrôleur ! Maintenant, si vous cliquez sur le nouveau bouton, vous verrez de nouvelles fenêtres s'afficher et elles vous présenteront l'état du jeu. Et encore mieux, elles s'actualiseront toutes en même temps !

Nous allons effectuer tout cela ensemble, entrons dans le vif du sujet :

Vous pouvez utiliser ce pattern lorsque vous disposez d'informations qui doivent être diffusées vers plusieurs objets. Par exemple, vous pouvez avoir besoin d'envoyer un e-mail ou un texte, d'écrire dans un journal et de mettre à jour un écran, car une modification a eu lieu dans votre application. Chacun des objets d'action (e-mail, texte, journal, écran) peut recevoir des instructions s’il implémente le pattern.

Le pattern Strategy

Dans sa forme la plus simple, le pattern Strategy est un polymorphisme. Quand vous disposez d'une famille d'algorithmes, vous pouvez chacun les encapsuler dans une classe qui implémente une interface commune

La façon de cuire une pièce de viande dans un restaurant est un exemple. Chaque cuisson, de "bleu" à "bien cuit", est réalisée de façon fondamentalement similaire. Le chef place la viande sur le grill pendant un certain temps, la retourne de l'autre côté et la cuit un peu plus. Ensuite, il teste sa cuisson. L'algorithme est sélectionné pour chaque personne, puis exécuté.

interface PrepareSteak {
    public void cook(Steak);
};

 
class RareSteak implements PrepareSteak {

    public void cook(Steak) {=
        /* pas trop longtemps */
    }
    
};

 
class MediumRare implements PrepareSteak {

    public void cook(Steak) {
        /* s'assurer que c'est cuit */
    }

};

 
class Medium implements PrepareSteak {

    public void cook(Steak) {
    /* garder le steak sur le grill un peu trop longtemps */
    }
};

 

class WellDoneSteak implements PrepareSteak {

    public void cook(Steak) {
        /* oublier le steak et revenir plus tard */
    }

};

Ainsi, dans l'exemple ci-dessus, quand une personne commande un steak, vous créez l'objet correspondant en fonction de la commande (bleu, saignant, à point ou bien cuit), mais la méthode appelée est toujours la même :   cook()  .

Ce pattern utilise le principe ouvert/fermé. Il permet de créer différentes implémentations selon les besoins, tandis que l'appelant des objets Strategy n'a jamais besoin d'être modifié. Utilisez ce pattern lorsque vous voulez permettre le choix d'une approche différente de celle pensée initialement, en toute flexibilité et sans avoir à réécrire d'autres parties de votre code.

Le pattern État

En Java, vous ne pouvez pas ajouter de façon dynamique des méthodes à un objet, ou les modifier une fois qu'elles ont été créées. Cependant, il arrive parfois d'avoir besoin qu'un objet se comporte différemment lorsque son état change. Une des solutions pourrait consister à ajouter de nombreuses instructions if et switch. Mais cela aboutirait à une conception confuse.

À la place, vous pourriez ajouter une nouvelle hiérarchie de classes qui encapsule ces différents comportements. L'objet utilisé par le client modifie ensuite l'objet initial, à mesure que son état change : c'est le pattern État !

Par exemple, je demande à mon fils d'aller me chercher un soda dans le réfrigérateur. Plutôt que de le faire lui-même, il appelle sa sœur en criant, puisqu'elle se trouve dans la cuisine. Elle choisit un soda qu'elle sait que j'apprécie, le sort du réfrigérateur et le lui lance. Mon fils me remet alors un soda vanille-orange.

Le lendemain, je redemande à mon fils de m'apporter un soda. Cette fois, un ami est dans la cuisine. Mon fils appelle son ami en criant, qui, lui, n'a aucune idée de ce que j'aime. L'ami choisit un soda au hasard et l'envoie à mon fils. Mon fils me ramène une eau gazeuse. Raté.

De mon point de vue, mon fils se comporte différemment (et pas seulement comme un adolescent). Hier, j'ai obtenu ce que je voulais. Aujourd'hui, cela n'a pas été le cas. Pourtant, mon fils n'a pas changé.

class Son implements Person {
    
    private Person personInKitchen;
    
    
    public void setPersonInKitchen(Person p) {
        personInKitchen = p;
    }
    

    public Soda getDadASoda() {
        return personInKitchen.getSodaFromFridge();
    }


    public Soda getSodaFromFridge() {
        return searchForGingerAle();
    }
    
};


class Daughter implements Person {
    
    public Soda getSodaFromFridge() {
        return searchForVanillaOrange();
    }
    
};


class SonsFriend implements Person {
    
    public Soda getSodaFromFridge() {
        return grabWhateverIFindFirst();
    }
    
};

Donc, dans le code ci-dessus, l'appel lancé au filsgetDadASoda() entraînera à son tour l'appel getSodaFromFridge() pour tout objet qui a comme variable déclaréepersonInKitchen . Étant donné que cette valeur peut changer, différents objets implémentent getSodaFromFridge() différemment. Néanmoins, j'appelle toujours la méthode getDadASoda() de mon fils.

En résumé

  • Le pattern Observateur permet à un objet d’observer le changement d’état d’un autre objet et d’agir en conséquence.

  • Le pattern Strategy permet de choisir à la volée d’exécuter certains algorithmes pour des situations spécifiques.

  • Le pattern État permet de changer le comportement d’un objet lorsque son état change.

Exemple de certificat de réussite
Exemple de certificat de réussite