• 6 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 16/02/2024

Utilisez les design patterns structuraux

Maîtrisez le design pattern Adapter

Le design pattern Adapter permet à deux objets avec des interfaces incompatibles de collaborer ensemble.

Imaginez que l’on vous demande de créer une application mobile qui affiche le prix de différentes cryptomonnaies : le Bitcoin, l’Ethereum, etc.

Vous avez trouvé une librairie développée dans votre langage favori qui vous permet de récupérer l’information au format JSON.

Mais récemment, OpenClassrooms a annoncé la sortie de sa propre monnaie : le SuccessCoin !

Une librairie a été conçue pour vous permettre d’accéder à sa valeur en équivalent Bitcoin, mais le résultat est renvoyé en XML et pas en JSON. :'(

Comment gérer les deux formats ? Toute mon application est faite pour recevoir du JSON !

Vous pouvez créer un adaptateur : une classe dont la responsabilité va être de convertir l’interface d’un objet de façon à ce qu’un autre objet puisse le comprendre.

L’adaptateur va donc réaliser la conversion. Dans notre cas, il va s’agir d’un convertisseur XML => JSON. Pourtant, sa responsabilité reste de respecter l’interface, et donc cette tâche nous est cachée.

De fait, l’objet adapté (celui qui provient de la librairie d’OpenClassrooms) n’est pas du tout au courant de la présence de son adaptateur : il est injecté et ne doit pas être changé.

L'adaptateur a une interface, compatible avec l'un des deux objets que l'on essaie de faire travailler de la même façon dans une application.  Ensuite, en se basant sur l'interface, l'application peut donc utiliser les fonctions sans risques.  A chaque
Le design pattern Adapter

Voici comment cela fonctionne :

  1. L’adaptateur a une interface, compatible avec l’un des deux objets que l’on essaie de faire travailler de la même façon dans une application.

  2. En se basant sur l’interface, l’application peut donc utiliser les fonctions de l'adaptateur sans risque.

  3. À chaque fois que l’adaptateur reçoit un appel, il s’occupe de faire les conversions en appelant les fonctions spécifiques de l’objet adapté.

Voici un exemple concret d’implémentation en PHP du design pattern Adapter, dans le cadre de la résolution du problème présenté.

Commençons par considérer les deux objets d’exemple.

D’abord, un objet actuellement utilisé de l’application :

<?php

namespace App\Currency;

class Bitcoin implements CryptoCurrency
{
    // ...
    public function getPriceAsJSON()
    {
        //
    }
}

Puis l’objet que nous souhaitons adapter de la librairie d’OpenClassrooms :

<?php

namespace OC\CryptoCurrency;

class SuccessCoin
{
    // ...
    
    /**
     * Retourne la valeur du SuccessCoin
     * en équivalent Bitcoin au format XML
     */ 
    public function export()
    {
        // ...
    }
}

Nous créons donc une interface dédiée à cette tâche qui doit respecter l’interface d’un des deux objets au minimum :

<?php

/**
 * Retourne la valeur de la crypto monnaie
 * en équivalent Bitcoin au format JSON
 */
interface CryptoValueJsonExporter
{
    public function getPriceAsJSON();
}

On crée maintenant l’adaptateur pour "supporter" le SuccessCoin :

<?php

use OC\CryptoCurrency\SuccessCoin;
use CryptoValueJsonExporter;

class SuccessCoinAdapter implements CryptoValueJsonExporter
{
    private $coin;

    public function __construct(SuccessCoin $successCoin)
    {
        $this->coin = $successCoin;
    }

    public function getPriceAsJSON()
    {
        $resultInXML = $this->coin->export();
        // fonction de transformation à créer...
        return $this->convertXMLtoJson($resultInXML);
    }
}

Et maintenant, quand l’application aura besoin d’obtenir une valeur en JSON, elle devra injecter une instance de CryptoValueJsonExporter et non plus l’un des objets décrits directement !

Maîtrisez le design pattern Composite

Le design pattern structurel Composite vous permet d’organiser vos objets sous forme d’arbre, et ensuite de pouvoir travailler avec chacun des éléments de cet arbre comme s’il était un objet indépendant.

Utiliser ce design pattern n’a de sens que si votre problème nécessite d’être modélisé sous forme d’arbre. Par exemple, imaginez une application permettant de représenter la hiérarchie de la République française, et notamment de son gouvernement :

En république Française, le Président choisit le Premier Ministre qui constituera un gouvernement composée de Ministres et de Secrétaires d'Etat. Rattachés à un Ministère, ils sont hiérarchiquement les supérieurs des hauts fonctionnaires de la f
Gouvernement français (représentation vulgarisée)

Vous imaginez la complexité de représenter cela, par exemple pour obtenir les réponses aux questions :

  • Quels sont les supérieurs et/ou subordonnés d’un membre du gouvernement ?

  • Quel est le budget d’un ministère en y rattachant l’ensemble des salaires de tous les membres le constituant ?

Le design pattern Composite suggère de faire implémenter une, voire plusieurs des interfaces communes entre ces différents objets :

Le design pattern Composite vous permet de structurer et d'organiser vos objets sous forme d'arbre et de travailler avec ces objets comme s'ils n'était qu'un seul et même objet
Le design pattern Composite
  1. L’interface ComponentInterface  décrit l’ensemble des opérations qui sont communes à tous les éléments de l’arbre.

  2. On appelle Leaf un élément qui n’a aucun "enfant" (ou sous-élément).

  3. On appelle Composite un élément qui a des enfants : le rôle d’un composite est de déléguer les opérations à effectuer à ses enfants, jusqu’à parvenir auxLeafs qui font les opérations effectives.

  4. Le Client peut aller chercher n’importe quel élément de l’arbre en le manipulant au travers de l’interface choisie.

Implémentons ce design pattern en PHP pour être capable de définir le budget du gouvernement, en imaginant que le budget corresponde à l’ensemble des salaires des collaborateurs.

Tout d’abord, implémentons l’interface avec les fonctions requises et la fonction qui permet de retrouver le budget.

<?php

interface Employee
{
   public function getBudget();
}

Le président de la République est un Composite (ou un employeur, si vous préférez).

Nous pouvons créer une interface pour cela, même si elle n’est pas strictement obligatoire dans l’implémentation du design pattern :

<?php

interface Employer
{
    public function getEmployees();

    public function addEmployee(Employee $employee);

    public function removeEmployee(Employee $employee);
}

// et une implémentation abstraite
abstract class SimpleEmployer
{
    protected $employees = [];

    public function getEmployees()
    {
        return $this->employees;
    }
    
    public function addEmployee(Employee $employee)
    {
        //
    }
    
    public function removeEmployee(Employee $employee)
    {
        //
    }
}

Implémentons notre président !

<?php

final class President extends SimpleEmployer implements Employee
{
    const PRESIDENT_SALARY = 80000;

    public function __construct()
    {
        $this->employees = [new PrimeMinister()];
    }

    public function getBudget()
    {
        $salary = self::PRESIDENT_SALARY;
        
        foreach ($this->getEmployees() as $employee) {
            $salary += $employee->getSalary();
        }
        
        return $salary;
    }
}

En demandant le budget d’un ministère, le système va simplement redescendre dans la hiérarchie en additionnant tous les salaires jusqu’au simple subordonné :

<?php

final class Teacher implements Employee
{
    const TEACHER_SALARY = 30000;
    
    public function getSalary()
    {
        return self::TEACHER_SALARY;
    }
}

Enfin, voici comment on pourrait "théoriquement" calculer le budget du ministère de l’Éducation :

<?php

$educationMinister = new EducationMinister(); // classe à créer

$educationBudget = $educationMinister->getBudget();

Maîtrisez le design pattern Decorator

Le design pattern Decorator vous permet d’ajouter dynamiquement de nouveaux comportements à un objet sans en modifier le code.

Imaginez un site de vente privée : à chaque nouvelle vente, le système envoie un e-mail à tous les utilisateurs inscrits, les invitant à se connecter et à profiter de l’offre.

Quand le client décide de créer son application mobile, il vous demande de pouvoir laisser le choix aux utilisateurs :

  • soit de recevoir des e-mails ;

  • soit de recevoir un SMS ;

  • soit les deux ;

  • soit... rien du tout.

Si nous modélisons cela sans réfléchir, nous finirons avec un nombre de classes enfants qui correspondront à chacune des possibilités (rien, "SMS + e-mail", "e-mail" et "SMS"), et si nous décidons d’ajouter des notifications push dans l’application mobile, le nombre de classes augmentera encore !

On peut penser que l’héritage de classe est la meilleure solution quand on veut altérer le comportement d’un objet, mais l’héritage de classe a un certain nombre de contraintes :

  • une fois héritée, une classe ne peut pas être changée : on dit que l’héritage est une altération statique ;

  • les classes enfants ne peuvent avoir qu’un seul parent en PHP, il n’est donc pas possible d’étendre une classe SMSNotifier et une classe PushNotifier en même temps.

Le design pattern Decorator permet d'ajouter de nouveaux comportements à des objets sans en changer le code
Le design pattern Decorator
  1. L’interface ComponentInterface  permet d’avoir une interface commune entre le décorateur et la classe décorée.

  2. Le Component  concret est une classe dont le comportement peut être enrichi par d’autres objets.

  3. L’interface DecoratorInterface  définit un champ ComponentInterface  qui permet la décoration. Ce décorateur de base délègue toutes les opérations réelles à l’objet  Component.

  4. Chaque décorateur concret peut définir un ou plusieurs comportements qui vont s’exécuter en complément du comportement originel du Component, avant ou après l’appel de la fonction d’origine (ici, la fonctiondoSomething()).

Implémentons la solution au problème décrit en PHP à l’aide du design pattern Decorator.

Tout d’abord, l’interface du  Component  (ici, un système de notification) :

<?php

interface NotifierInterface
{
    public function notify();
}

et l’interface du décorateur, qui en PHP est identique, si l’on ne souhaite pas rendre public l’accès au  Component  décoré :

<?php

interface NotifierDecoratorInterface
{
    public function notify();
}

Maintenant, créons un décorateur :

<?php

final class SMSNotifier implements ComponentInterface, NotifierDecoratorInterface
{
    private $component;

    public function __construct(ComponentInterface $component = null)
    {
        $this->component = $component;
    }

    public function notify()
    {
        if ($this->component !== null) {
            $this->component->notify();
        }
        
        
        $this->sendSMS();
    }

    private function sendSMS()
    {
        //
    }
}

Il ne reste qu’à mettre en place un client capable d’appeler les différents décorateurs :

<?php

use NullNotifier;
use SMSNotifier;
use EmailNotifier;
use PushNotifier;

// les 3 en même temps
$fullNotifier = new SMSNotifier(new EmailNotifier(new PushNotifier));

// seulement SMS et Push ? facile !
$mobileNotifier = new PushNotifier(new SMSNotifier());

// aucune notification ?

$nullNotifier = new NullNotifier();

class Client
{
    private $notifier;
    
    public function __construct(NotifierInterface $notifier)
    {
        $this->notifier = $notifier;
    }

    public function notify()
    {
        return $this->notifier->notify();
    }
}

Comme vous pouvez le remarquer, la classe Client   ne changera pas en fonction du nombre de systèmes de notification supportés par l’application, ou en fonction des choix de l’utilisateur : ce code est solide.

Exercez-vous !

OpenClassrooms vous demande d'intégrer son Success Coin dans votre application, et malheureusement ce n'est pas tout à fait faisable : bien qu'ils aient une classe en PHP à vous fournir, elle ne respecte pas vraiment les choix que vous aviez faits au départ...

Vous allez donc devoir créer un adaptateur entre vos deux implémentations !

Comme pour les chapitres précédents, téléchargez le projet de démarrage et suivez les commentaires pour finaliser le projet complet !

Commençons par la vue d'ensemble du projet dans le screencast ci-dessous :

Maintenant, suivez-moi dans la correction dans le screencast suivant :

En résumé

Les design patterns structuraux permettent d’assembler des objets et classes en structures plus grandes, tout en permettant de conserver le code flexible et efficace.

Nous avons traité trois problèmes communs en développement logiciel :

  • Si vous êtes dans une situation où certaines parties de votre application sont incompatibles entre elles, mais doivent collaborer autour d’une interface commune, il faudra implémenter le design pattern Adapter.

  • Parfois, votre modèle est structuré sous forme d’un arbre où la relation enfant-parent est au cœur du système. Dans ce cas, ne tombez pas dans le piège de l’héritage ! Essayez plutôt d’implémenter le design pattern Composite.

  • Enfin, une bonne pratique logicielle consiste à favoriser la composition plutôt que l’héritage quand il s’agit d’ajouter de nouveaux comportements à un objet. Le design pattern Decorator répond à ce besoin, et il est très populaire en PHP.

Dans le prochain et dernier chapitre de ce cours, nous allons aborder une série de design patterns comportementaux. À tout de suite !

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