Skip to content

aratinau/design-patterns-for-humans-fr

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Design Patterns For Humans


🎉 Introduction ultra-simplifiée aux design patterns! 🎉

Un sujet qui peut facilement faire peur. Je vais ici essayer de vous les faire comprendre (et moi aussi par la même occasion) en les expliquant de la manière la plus simple possible.

***

🚀 Introduction

Les design patterns (patrons de conception en Français) sont des solutions à des problèmes récurrents. Ce ne sont pas des classes, packages ou libraries que vous pourrez connecter à votre application, pour ensuite attendre que la magie fasse effet. Ce sont plutôt des directives pour résoudre certains problèmes dans certaines situations.

Les design patterns sont des solutions récurrentes à des problèmes récurrents, des directives pour résoudre certains problèmes.

Wikipedia les décrit ainsi :

En informatique, et plus particulièrement en développement logiciel, un patron de conception (plus souvent appelé design pattern) est un arrangement caractéristique de modules, reconnu comme bonne pratique en réponse à un problème de conception d'un logiciel. Il décrit une solution standard, utilisable dans la conception de différents logiciels.

⚠️ À savoir

  • Les design patterns ne sont pas des solutions miracles à tous.
  • N'essayez pas de vous forcer à les utiliser dans un projet. Gardez à l'esprit que les design patterns sont des solutions à des problèmes et non pas des solutions pour trouver des problèmes.
  • S'ils sont utilisés au bon endroit et de la bonne manière, ils peuvent s'avérer salvateurs ; dans le cas contraire ils risquent d'entraîner une complexité du code inutile et dommageable.

Types de design patterns

Design patterns de création

En clair

Les patterns de créations sont centrés sur la façon d'instancier un objet ou un groupe d'objets liés.

D'après Wikipédia

Dans le génie logiciel, les design patterns de création sont des patrons de conception qui traitent des mécanismes de création d'objet en essayant de créer les objets d'une façon appropriée à la situation. La forme de base de la création d'objet pourrait entraîner des problèmes de conception ou ajouter de la complexité à la conception. Les design patterns de création résolvent ce problème en contrôlant la création d'objets d'une certaine manière.

🏠 Simple Factory

Un exemple dans le monde réel

Considérez que vous construisez une maison et vous avez besoin de portes. Ce serait un gâchis si chaque fois que vous avez besoin d'une porte, vous mettiez vos vêtements de menuisier(ère) et commenciez à construire une porte dans votre maison. Au lieu de cela vous le faites dans une usine.

En clair

Simple Factory génère simplement une instance pour le client sans exposer toute la logique d'instanciation au client.

D'après Wikipédia

Dans la programmation orientée objet (POO), une Factory est un objet qui crée d'autres objets – formellement une Factory est une fonction ou méthode qui retourne des objets d'un prototype ou d'une classe variable à partir d'un appel de méthode, qui est supposé être "new".

Exemple de programme

Tout d'abord, nous avons une interface de porte (Door) et son implémentation

interface Door 
{
    public function getWidth(): float;
    public function getHeight(): float;
}

class WoodenDoor implements Door
{
    protected $width;
    protected $height;

    public function __construct(float $width, float $height)
    {
        $this->width = $width;
        $this->height = $height;
    }

    public function getWidth(): float
    {
        return $this->width;
    }

    public function getHeight(): float
    {
        return $this->height;
    }
}

Alors nous avons notre Factory de porte (DoorFactory) qui construit la porte et la renvoie

class DoorFactory
{
    public static function makeDoor($width, $height): Door
    {
        return new WoodenDoor($width, $height);
    }
}

Elle peut être utilisée comme ceci

$door = DoorFactory::makeDoor(100, 200);
echo 'Width: ' . $door->getWidth();
echo 'Height: ' . $door->getHeight();

Quand l'utiliser ?

Quand la création d'objet n'est pas seulement une affectation et implique une certaine logique, cela a du sens de déplacer cette logique dans une Factory plutôt que de répéter le même code partout.

🏭 Factory Method

Un exemple dans le monde réel

Considérez le travail d'un(e) DRH dans une grande structure. Il est impossible pour une seule personne de conduire les entretiens pour tous les types de postes. Elle doit alors définir les étapes d'un entretien, et les déléguer à différentes personnes compétentes.

En clair

Factory Method fournit un moyen de déléguer la logique d'instanciation aux classes enfants.

D'après Wikipédia

Dans la programmation à base de classe, le pattern Factory Method est un pattern créationnel qui utilise des patterns Factory pour résoudre le problème de création d'objets sans devoir spécifier la classe exacte de l'objet qui sera créé. Pour ce faire, on crée des objets via l’appel d’une Factory Method - soit spécifiée dans une interface et implémentée dans une classe enfant, soit implémentée dans une classe de base et éventuellement surchargée par des classes dérivées plutôt que d'appeler un constructeur.

Exemple de programme

Prenons l'exemple de notre DRH. Tout d'abord, nous disposons d'une interface d'intervieweur et de certaines implémentations

interface Interviewer
{
    public function askQuestions();
}

class Developer implements Interviewer
{
    public function askQuestions()
    {
        echo 'Asking about design patterns!';
    }
}

class CommunityExecutive implements Interviewer
{
    public function askQuestions()
    {
        echo 'Asking about community building';
    }
}

Maintenant, créons notre HiringManager

abstract class HiringManager
{

    // Factory method
    abstract public function makeInterviewer(): Interviewer;

    public function takeInterview()
    {
        $interviewer = $this->makeInterviewer();
        $interviewer->askQuestions();
    }
}

À présent, n'importe quel enfant peut étendre et fournir l'intervieweur requis

class DevelopmentManager extends HiringManager
{
    public function makeInterviewer(): Interviewer
    {
        return new Developer();
    }
}

class MarketingManager extends HiringManager
{
    public function makeInterviewer(): Interviewer
    {
        return new CommunityExecutive();
    }
}

On pourra ensuite utiliser ce code comme ceci

$devManager = new DevelopmentManager();
$devManager->takeInterview(); // Output: Asking about design patterns

$marketingManager = new MarketingManager();
$marketingManager->takeInterview(); // Output: Asking about community building.

Quand l'utiliser ?

Utile quand il y a du traitement générique dans une classe, mais la sous-classe requise est déterminée dynamiquement lors de l'exécution. Ou, en d'autres termes, lorsque le client ne sait pas de quelle sous-classe exacte il pourrait avoir besoin.

🔨 Abstract Factory

Un exemple dans le monde réel

Étendons notre exemple de porte sur la base de Simple Factory. En fonction de vos besoins vous pouvez obtenir une porte en bois, en fer ou en PVC respectivement depuis des magasins de portes en bois, en fer ou spécialisés. De plus vous aurez besoin de différents artisans pour fixer la porte : par exemple un(e) charpentier(ère) pour la porte en bois, un(e) soudeur(se) pour la porte en fer, etc. Comme vous pouvez le voir les portes sont maintenant dépendantes d'une autre entité : une porte en bois à besoin d'un(e) charpentier(ère), une porte en fer à besoins d'un(e) soudeur(se), etc.

En clair

Il s'agit d'une usine de Factory (une usine d'usines) : une Factory qui regroupe les Factory individuelles mais dépendantes, sans spécifier leurs classes concrètes.

D'après Wikipédia

Le pattern Abstract Factory offre un moyen d'encapsuler un groupe de Factory individuelles qui ont un thème commun sans spécifier leurs classes concrètes.

Exemple de programme

Traduction de l'exemple de la porte (Door) ci-dessus. Tout d'abord nous avons notre interface Door et quelques implémentations

interface Door
{
    public function getDescription();
}

class WoodenDoor implements Door
{
    public function getDescription()
    {
        echo 'I am a wooden door';
    }
}

class IronDoor implements Door
{
    public function getDescription()
    {
        echo 'I am an iron door';
    }
}

Ensuite, nous avons quelques experts appropriés pour chaque type de porte

interface DoorFittingExpert
{
    public function getDescription();
}

class Welder implements DoorFittingExpert
{
    public function getDescription()
    {
        echo 'I can only fit iron doors';
    }
}

class Carpenter implements DoorFittingExpert
{
    public function getDescription()
    {
        echo 'I can only fit wooden doors';
    }
}

Maintenant, nous avons notre Abstract Factory qui nous permettrait de créer une famille d'objets liés, c'est-à-dire que le Factory de la porte (WoodenDoorFactory) en bois créerait une porte en bois et un spécialiste de porte en bois (un Charpentier) et le Factory de la porte en fer (IronDoorFactory) créerait une porte en fer et un spécialiste de porte en fer (un Soudeur).

interface DoorFactory
{
    public function makeDoor(): Door;
    public function makeFittingExpert(): DoorFittingExpert;
}

// Factory de la porte en bois qui retourne un charpentier et une porte en bois
class WoodenDoorFactory implements DoorFactory
{
    public function makeDoor(): Door
    {
        return new WoodenDoor();
    }

    public function makeFittingExpert(): DoorFittingExpert
    {
        return new Carpenter();
    }
}

// Factory de la porte en fer qui retourne une porte en fer et un soudeur
class IronDoorFactory implements DoorFactory
{
    public function makeDoor(): Door
    {
        return new IronDoor();
    }

    public function makeFittingExpert(): DoorFittingExpert
    {
        return new Welder();
    }
}

La Factory peut ensuite être utilisée comme ceci

$woodenFactory = new WoodenDoorFactory();

$door = $woodenFactory->makeDoor();
$expert = $woodenFactory->makeFittingExpert();

$door->getDescription();  // Output: I am a wooden door
$expert->getDescription(); // Output: I can only fit wooden doors

// Idem pour le Factory de porte en fer
$ironFactory = new IronDoorFactory();

$door = $ironFactory->makeDoor();
$expert = $ironFactory->makeFittingExpert();

$door->getDescription();  // Output: I am an iron door
$expert->getDescription(); // Output: I can only fit iron doors

Comme vous pouvez le voir, la Factory de la porte (WoodenDoorFactory) encapsule le charpentier (Carpenter) et la porte en bois (WoodenDoor), et la Factory de la porte en fer (IronDoorFactory) encapsule la porte en fer (IronDoor) et le soudeur (Welder). Cela nous assure que pour chacune des portes créées, nous aurons le bon artisan.

Quand l'utiliser ?

Quand la logique de création se complique et implique des dépendances.

👷 Builder

Un exemple dans le monde réel

Imaginez que vous êtes chez McDonald's, vous passez commande, disons d'un "Big Mac" et le(la) serveur(se) vous le remet sans poser de questions ; voici un exemple de Simple Factory. Mais il y a des cas où la logique de création implique plus d'étapes, comme chez Subway : quel pain voulez-vous, quelle viande, quel fromage, quelle sauce… C'est dans de telles situations que nous allons utiliser le pattern Builder.

En clair

Un Builder permet de créer des versions différentes d’un objet tout en évitant de polluer le constructeur. Utile quand un objet existe en de multiples variations, ou lorsque sa création nécessite beaucoup d’étapes.

D'après Wikipédia

Le pattern Builder est un patron de conception de création d’objet avec l’intention de trouver une solution à l’anti-pattern de constructeur télescopique (ou condenser).

Pour illustrer celà, voici à quoi ressemble l'anti-pattern du constructeur télescopique. À un moment où à un autre, nous avons tous vu/écrit ce genre de constructeur :

public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true)
{
}

Comme vous pouvez le voir le nombre de paramètres du constructeur peut rapidement devenir incontrôlable et il peut être difficile de comprendre la disposition de ces paramètres. De plus, cette liste pourrait continuer à augmenter si vous souhaitez ajouter d'autres options à l'avenir. C'est ce qu'on appelle un constructeur télescopique.

Exemple de programme

L'alternative raisonnable est d'utiliser le pattern Builder. Tout d'abord, nous avons notre hamburger que nous voulons créer.

class Burger
{
    protected $size;

    protected $cheese = false;
    protected $pepperoni = false;
    protected $lettuce = false;
    protected $tomato = false;

    public function __construct(BurgerBuilder $builder)
    {
        $this->size = $builder->size;
        $this->cheese = $builder->cheese;
        $this->pepperoni = $builder->pepperoni;
        $this->lettuce = $builder->lettuce;
        $this->tomato = $builder->tomato;
    }
}

Et ensuite nous avons le Builder

class BurgerBuilder
{
    public $size;

    public $cheese = false;
    public $pepperoni = false;
    public $lettuce = false;
    public $tomato = false;

    public function __construct(int $size)
    {
        $this->size = $size;
    }

    public function addPepperoni()
    {
        $this->pepperoni = true;
        return $this;
    }

    public function addLettuce()
    {
        $this->lettuce = true;
        return $this;
    }

    public function addCheese()
    {
        $this->cheese = true;
        return $this;
    }

    public function addTomato()
    {
        $this->tomato = true;
        return $this;
    }

    public function build(): Burger
    {
        return new Burger($this);
    }
}

Il peut alors être utilisé comme ceci

$burger = (new BurgerBuilder(14))
                    ->addPepperoni()
                    ->addLettuce()
                    ->addTomato()
                    ->build();

Quand l'utiliser ?

Quand il peut y avoir plusieurs variantes d'un objet et pour éviter le constructeur télescopique. La principale différence par rapport au pattern Factory est que ce dernier doit être utilisé lorsque la création est un processus en une seule étape, tandis que le pattern Builder doit être utilisé lorsque la création est un processus en plusieurs étapes.

🐑 Prototype

Un exemple dans le monde réel

Vous vous souvenez de Dolly ? Le mouton cloné ! N'entrons pas dans les détails mais nous allons parler de clonage.

En clair

Crée un objet en se basant sur un objet existant (comme du clonage).

D'après Wikipédia

Le patron de conception Prototype est utilisé lorsque la création d'une instance est complexe ou consommatrice en temps. Plutôt que créer plusieurs instances de la classe, on copie la première instance et on modifie la copie de façon appropriée.

En gros, il permet de créer une copie d'un objet existant en le modifiant selon nos besoins, au lieu de devoir recréer un nouvel objet à partir de zéro.

Exemple de programme

C'est faisable très facilement en PHP en utilisant clone, voici déjà une classe pour notre mouton

class Sheep
{
    protected $name;
    protected $category;

    public function __construct(string $name, string $category = 'Mountain Sheep')
    {
        $this->name = $name;
        $this->category = $category;
    }

    public function setName(string $name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setCategory(string $category)
    {
        $this->category = $category;
    }

    public function getCategory()
    {
        return $this->category;
    }
}

On peut ensuite le cloner comme ceci

$original = new Sheep('Jolly');
echo $original->getName(); // Jolly
echo $original->getCategory(); // Mountain Sheep

// Clone and modify what is required
$cloned = clone $original;
$cloned->setName('Dolly');
echo $cloned->getName(); // Dolly
echo $cloned->getCategory(); // Mountain sheep

On peut également utiliser la méthode magique __clone pour modifier le comportement du clonage.

Quand l'utiliser ?

Quand on a besoin d'un objet similaire à un objet existant ou quand la création d'un tout nouvel objet serait trop coûteuse par rapport à un clonage.

💍 Singleton

Un exemple dans le monde réel

Il ne peut y avoir qu'un seul président d'un pays à la fois. À chaque fois que le devoir l'appelle, c'est le même président qui agit. Le président est ici un Singleton.

En clair

S'assure qu'une classe ne peut être instanciée qu'une seule et unique fois.

D'après Wikipédia

En génie logiciel, le singleton est un patron de conception dont l'objectif est de restreindre l'instanciation d'une classe à un seul objet (ou bien à quelques objets seulement). Il est utilisé lorsqu'on a besoin exactement d'un objet pour coordonner des opérations dans un système.

Le pattern Singleton est en fait considéré comme un anti-pattern et il faut éviter d'en abuser. Il n'est pas nécessairement mauvais et présente quelques cas d'utilisation valides, mais il doit être utilisé avec précaution car il introduit un état global dans l'application. Des changements à l'intérieur du Singleton pourraient avoir des effets secondaires à d'autres endroits et le debug peut s'avérer difficile. Une autre mauvaise conséquence de son utilisation est qu'il rend le code fortement couplé et les tests unitaires s'en retrouvent d'autant plus compliqués.

Exemple de programme

Pour créer un Singleton, il faut rendre le constructeur privé, empêcher le clonage, empêcher l'héritage et créer une variable statique qui va accueillir l'instance globale

final class President
{
    private static $instance;

    private function __construct()
    {
        // Hide the constructor
    }

    public static function getInstance(): President
    {
        if (!self::$instance) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    private function __clone()
    {
        // Disable cloning
    }

    private function __wakeup()
    {
        // Disable unserialize
    }
}

Il peut ensuite être utilisé comme ceci

$president1 = President::getInstance();
$president2 = President::getInstance();

var_dump($president1 === $president2); // true

Design Patterns Structurels

En clair

Les Patterns Structurels (ou d'architecture) s'occupent principalement de la composition entre objets ou de la manière dont les objets interagissent entre eux. Une autre explication serait qu'ils répondent à la question : "Comment construire un composant logiciel ?"

D'après Wikipédia

En informatique, un pattern structurel est un patron de conception, c'est-à-dire un modèle de référence qui sert de source d'inspiration lors de la conception de l'architecture d'un système ou d'un logiciel informatique en sous-éléments plus simples.

🔌 Adapter

Un exemple dans le monde réel

Imaginons que vous souhaitiez transférer des images d'une carte mémoire à votre ordinateur. Pour les transférer, vous avez besoin d'un adaptateur compatible avec les ports de votre ordinateur de manière à y insérer votre carte mémoire. Dans ce cas, le lecteur de carte est un adaptateur. Un exemple supplémentaire serait celui du traducteur, qui traduit les mots prononcés par une personne dans la langue d'une autre.

En clair

Le Pattern Adapter permet d'encapsuler un objet (incompatible en l'état) pour le rendre compatible avec une autre classe.

D'après Wikipédia

En génie logiciel, Adapter (ou Wrapper) est un patron de conception de type structurel. Il permet de convertir l'interface d'une classe en une autre interface que le client attend.

Exemple de programme

Soit un jeu dans lequel se trouve un chasseur chassant des lions.

Nous avons d'abord une interface Lion que tous les lions doivent implémenter.

interface Lion
{
    public function roar();
}

class AfricanLion implements Lion
{
    public function roar()
    {
    }
}

class AsianLion implements Lion
{
    public function roar()
    {
    }
}

Le chasseur chasse n'importe quelle implémentation de l'interface Lion.

class Hunter
{
    public function hunt(Lion $lion)
    {
    }
}

Maintenant nous voulons ajouter un WildDog dans notre jeu, que le chasseur pourrait aussi chasser. Malheureusement nous ne pouvons pas l'ajouter directement car ce chien a une interface différente. Pour le rendre compatible avec notre chasseur, nous devons créer un Adapter compatible.

// Ceci doit être ajouté au jeu
class WildDog
{
    public function bark()
    {
    }
}

// Adapter encapsule WildDog pour le rendre compatible avec notre jeu
class WildDogAdapter implements Lion
{
    protected $dog;

    public function __construct(WildDog $dog)
    {
        $this->dog = $dog;
    }

    public function roar()
    {
        $this->dog->bark();
    }
}

A présent le WildDog peut être utilisé dans notre jeu en utilisant WildDogAdapter

$wildDog = new WildDog();
$wildDogAdapter = new WildDogAdapter($wildDog);

$hunter = new Hunter();
$hunter->hunt($wildDogAdapter);

🚡 Bridge

Un exemple dans le monde réel

Considérons que vous ayez un site internet constitué de différentes pages et que vous êtes censé permettre à l'utilisateur de changer le thème du site. Comment feriez-vous : créer une copie de chaque page pour chaque thème, ou juste créer des thèmes chargés en fonction des préférences de l'utilisateur ? Le pattern Bridge vous permet de réaliser la seconde solution :

With and without the bridge pattern

En clair

Le pattern Bridge consiste à privilégier la composition par rapport à l'héritage. Les détails d'implémentation sont déplacés de la hiérarchie de l'objet courant, vers un autre objet doté d'une hiérarchie différente.

D'après Wikipédia

Le pont est un patron de conception structurel, qui permet de découpler l'interface d'une classe et son implémentation. La partie concrète (implémentation réelle) peut alors varier, indépendamment de celle abstraite (définition virtuelle), tant qu'elle respecte le contrat de réécriture associé qui les lie (obligation de se conformer aux signatures des fonctions/méthodes, et de leurs fournir un corps physique d'implémentation).

Exemple de programme

Traduisons notre exemple de pages Web sous forme d'une hiérarchie de WebPage :

interface WebPage
{
    public function __construct(Theme $theme);
    public function getContent();
}

class About implements WebPage
{
    protected $theme;

    public function __construct(Theme $theme)
    {
        $this->theme = $theme;
    }

    public function getContent()
    {
        return "About page in " . $this->theme->getColor();
    }
}

class Careers implements WebPage
{
    protected $theme;

    public function __construct(Theme $theme)
    {
        $this->theme = $theme;
    }

    public function getContent()
    {
        return "Careers page in " . $this->theme->getColor();
    }
}

Voici la hiérarchie des thèmes :

interface Theme
{
    public function getColor();
}

class DarkTheme implements Theme
{
    public function getColor()
    {
        return 'Dark Black';
    }
}
class LightTheme implements Theme
{
    public function getColor()
    {
        return 'Off white';
    }
}
class AquaTheme implements Theme
{
    public function getColor()
    {
        return 'Light blue';
    }
}

Une fois réunies, on obtient :

$darkTheme = new DarkTheme();

$about = new About($darkTheme);
$careers = new Careers($darkTheme);

echo $about->getContent(); // "About page in Dark Black";
echo $careers->getContent(); // "Careers page in Dark Black";

🌿 Composite

Un exemple dans le monde réel

Toutes les entreprises sont composées d'employés. Chaque employé possède les mêmes caractéristiques : un salaire, des responsabilités, un supérieur hiérarchique, des subordonnés, etc.

En clair

Le Pattern Composite permet au client de traiter chaque objet de manière uniforme.

D'après Wikipédia

En génie logiciel, un objet Composite est un patron de conception structurel. Ce patron permet de concevoir une structure d'arbre, par exemple un arbre binaire en limitant à deux le nombre de sous-éléments.

Exemple de programme

En reprenant l'exemple ci-dessus, nous avons plusieurs types d'employés :

interface Employee
{
    public function __construct(string $name, float $salary);
    public function getName(): string;
    public function setSalary(float $salary);
    public function getSalary(): float;
    public function getRoles(): array;
}

class Developer implements Employee
{
    protected $salary;
    protected $name;

    public function __construct(string $name, float $salary)
    {
        $this->name = $name;
        $this->salary = $salary;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setSalary(float $salary)
    {
        $this->salary = $salary;
    }

    public function getSalary(): float
    {
        return $this->salary;
    }

    public function getRoles(): array
    {
        return $this->roles;
    }
}

class Designer implements Employee
{
    protected $salary;
    protected $name;

    public function __construct(string $name, float $salary)
    {
        $this->name = $name;
        $this->salary = $salary;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setSalary(float $salary)
    {
        $this->salary = $salary;
    }

    public function getSalary(): float
    {
        return $this->salary;
    }

    public function getRoles(): array
    {
        return $this->roles;
    }
}

Ensuite nous avons une entreprise qui consiste simplement en plusieurs employés :

class Organization
{
    protected $employees;

    public function addEmployee(Employee $employee)
    {
        $this->employees[] = $employee;
    }

    public function getNetSalaries(): float
    {
        $netSalary = 0;

        foreach ($this->employees as $employee) {
            $netSalary += $employee->getSalary();
        }

        return $netSalary;
    }
}

On peut ensuite l'utiliser comme ceci :

// On prepare les employés
$john = new Developer('John', 10000);
$jane = new Designer('Jane', 12000);

// On les ajoute à l'entreprise
$organization = new Organization();
$organization->addEmployee($john);
$organization->addEmployee($jane);

echo "Net salaries: " . $organization->getNetSalaries(); // Net Salaries: 22000

☕ Decorator

Un exemple dans le monde réel

Imaginez que vous gériez un magasin de services automobiles. Comment calculez-vous le montant à facturer ? Vous choisissez un service et continuez à y ajouter les prix pour les services fournis jusqu'à ce que vous obteniez le coût final. Ici, chaque type de service est un décorateur.

En clair

Le pattern Decorator vous permet de modifier dynamiquement le comportement d'un objet au moment de l'exécution en les enveloppant dans un objet d'une classe de décorateur.

D'après Wikipédia

Dans la programmation orientée objet, le pattern Decorator est un patron de conception logicielle qui permet d'ajouter un comportement à un objet individuel, de manière statique ou dynamique, sans affecter le comportement d'autres objets de la même classe. Le pattern Decorator est souvent utile pour adhérer au principe de responsabilité unique, car il permet de répartir les fonctionnalités entre les classes avec des domaines de préoccupation uniques.

Exemple de programme

Prenons l’exemple d’un café. Tout d'abord, nous avons un simple café (SimpleCoffee) qui implémente l'interface café

interface Coffee
{
    public function getCost();
    public function getDescription();
}

class SimpleCoffee implements Coffee
{
    public function getCost()
    {
        return 10;
    }

    public function getDescription()
    {
        return 'Simple coffee';
    }
}

Nous voulons rendre le code extensible pour permettre aux options de le modifier si nécessaire. Faisons quelques add-ons (décorateurs)

class MilkCoffee implements Coffee
{
    protected $coffee;

    public function __construct(Coffee $coffee)
    {
        $this->coffee = $coffee;
    }

    public function getCost()
    {
        return $this->coffee->getCost() + 2;
    }

    public function getDescription()
    {
        return $this->coffee->getDescription() . ', milk';
    }
}

class WhipCoffee implements Coffee
{
    protected $coffee;

    public function __construct(Coffee $coffee)
    {
        $this->coffee = $coffee;
    }

    public function getCost()
    {
        return $this->coffee->getCost() + 5;
    }

    public function getDescription()
    {
        return $this->coffee->getDescription() . ', whip';
    }
}

class VanillaCoffee implements Coffee
{
    protected $coffee;

    public function __construct(Coffee $coffee)
    {
        $this->coffee = $coffee;
    }

    public function getCost()
    {
        return $this->coffee->getCost() + 3;
    }

    public function getDescription()
    {
        return $this->coffee->getDescription() . ', vanilla';
    }
}

Faisons un café maintenant

$someCoffee = new SimpleCoffee();
echo $someCoffee->getCost(); // 10
echo $someCoffee->getDescription(); // Simple Coffee

$someCoffee = new MilkCoffee($someCoffee);
echo $someCoffee->getCost(); // 12
echo $someCoffee->getDescription(); // Simple Coffee, milk

$someCoffee = new WhipCoffee($someCoffee);
echo $someCoffee->getCost(); // 17
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip

$someCoffee = new VanillaCoffee($someCoffee);
echo $someCoffee->getCost(); // 20
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip, vanilla

📦 Facade

Un exemple dans le monde réel

Comment démarrer un ordinateur ? "Appuyer sur le bouton d’alimentation" dites-vous ?! C’est ce que vous croyez parce que vous utilisez une interface simple que l’ordinateur vous fournit. À l'intérieur, il se passe de nopbreuses choses pour que l'ordinateur démarre réellement. Cette interface simple au sous-système complexe est une façade.

En clair

Le pattern Facade fournit une interface simplifiée à un sous-système complexe.

D'après Wikipédia

Une Facade est un objet qui fournit une interface simplifiée à un grand nombre de codes, comme une bibliothèque de classe.

Exemple de programme

Prenons l’exemple de l’ordinateur ci-dessus, nous avons ici sa classe (Computer)

class Computer
{
    public function getElectricShock()
    {
        echo "Ouch!";
    }

    public function makeSound()
    {
        echo "Beep beep!";
    }

    public function showLoadingScreen()
    {
        echo "Loading..";
    }

    public function bam()
    {
        echo "Ready to be used!";
    }

    public function closeEverything()
    {
        echo "Bup bup bup buzzzz!";
    }

    public function sooth()
    {
        echo "Zzzzz";
    }

    public function pullCurrent()
    {
        echo "Haaah!";
    }
}

En voici la Facade

class ComputerFacade
{
    protected $computer;

    public function __construct(Computer $computer)
    {
        $this->computer = $computer;
    }

    public function turnOn()
    {
        $this->computer->getElectricShock();
        $this->computer->makeSound();
        $this->computer->showLoadingScreen();
        $this->computer->bam();
    }

    public function turnOff()
    {
        $this->computer->closeEverything();
        $this->computer->pullCurrent();
        $this->computer->sooth();
    }
}

Maintenant, nous pouvons utiliser la Facade

$computer = new ComputerFacade(new Computer());
$computer->turnOn(); // Ouch! Beep beep! Loading.. Ready to be used!
$computer->turnOff(); // Bup bup buzzz! Haah! Zzzzz

🍃 Flyweight (poids plume)

Un exemple dans le monde réel

Avez-vous déjà bu du thé frais dans une échoppe ? Souvent, ils préparent plus d'une tasse que vous avez demandée et gardent le reste pour un autre client afin d'économiser les ressources, par exemple l'essence, etc. C'est ce que propose le modèle Flyweight, c'est-à-dire le partage.

En clair

Il est utilisé pour minimiser l'utilisation de la mémoire ou les dépenses de calcul en partageant autant que possible avec des objets similaires.

D'après Wikipédia

En programmation informatique, le poids plume est un modèle de conception de logiciel. Un poids plume est un objet qui minimise l'utilisation de la mémoire en partageant autant de données que possible avec d'autres objets similaires ; c'est un moyen d'utiliser des objets en grand nombre lorsqu'une simple représentation répétée utiliserait une quantité inacceptable de mémoire.

Exemple de programme

Traduction de notre example de thé ci-dessus. Tout d'abord, nous avons les types de thé et les fabricants de thé.

// Tout ce qui sera mis en cache est un poids plume.
// Les types de thé ici seront des Flyweight.
class KarakTea
{
}

// Agit comme une usine et enregistre le thé
class TeaMaker
{
    protected $availableTea = [];

    public function make($preference)
    {
        if (empty($this->availableTea[$preference])) {
            $this->availableTea[$preference] = new KarakTea();
        }

        return $this->availableTea[$preference];
    }
}

Ensuite, il y a le TeaShop qui prend les commandes et les sert.

class TeaShop
{
    protected $orders;
    protected $teaMaker;

    public function __construct(TeaMaker $teaMaker)
    {
        $this->teaMaker = $teaMaker;
    }

    public function takeOrder(string $teaType, int $table)
    {
        $this->orders[$table] = $this->teaMaker->make($teaType);
    }

    public function serve()
    {
        foreach ($this->orders as $table => $tea) {
            echo "Servir le thé à la table# " . $table;
        }
    }
}

Il peut être utilisé comme suit

$teaMaker = new TeaMaker();
$shop = new TeaShop($teaMaker);

$shop->takeOrder('less sugar', 1);
$shop->takeOrder('more milk', 2);
$shop->takeOrder('without sugar', 5);

$shop->serve();
// Servir le thé à la table# 1
// Servir le thé à la table# 2
// Servir le thé à la table# 5

🎱 Proxy

Un exemple dans le monde réel

Avez-vous déjà utilisé une carte d'accès pour franchir une porte ? Il existe plusieurs options pour ouvrir cette porte, c'est-à-dire qu'elle peut être ouverte soit à l'aide d'une carte d'accès, soit en appuyant sur un bouton qui permet de contourner la sécurité. La principale fonction de la porte est de s'ouvrir, mais un proxy a été ajouté pour ajouter des fonctionnalités. Permettez-moi de mieux l'expliquer à l'aide de l'exemple de code ci-dessous.

En clair

En utilisant le modèle proxy, une classe représente la fonctionnalité d'une autre classe.

D'après Wikipédia

Un proxy, dans sa forme la plus générale, est une classe fonctionnant comme une interface avec quelque chose d'autre. Un proxy est une enveloppe ou un objet agent qui est appelé par le client pour accéder à l'objet de service réel dans les coulisses. L'utilisation du proxy peut simplement consister en un transfert vers l'objet réel, ou peut fournir une logique supplémentaire. Dans le proxy, des fonctionnalités supplémentaires peuvent être fournies, par exemple la mise en cache lorsque les opérations sur l'objet réel sont gourmandes en ressources, ou la vérification des conditions préalables avant que les opérations sur l'objet réel ne soient invoquées.

Exemple de programme

Reprenons l'exemple de la porte de sécurité. Nous disposons tout d'abord de l'interface de la porte et d'une implémentation de la porte

interface Door
{
    public function open();
    public function close();
}

class LabDoor implements Door
{
    public function open()
    {
        echo "Opening lab door";
    }

    public function close()
    {
        echo "Closing the lab door";
    }
}

Then we have a proxy to secure any doors that we want

class Security
{
    protected $door;

    public function __construct(Door $door)
    {
        $this->door = $door;
    }

    public function open($password)
    {
        if ($this->authenticate($password)) {
            $this->door->open();
        } else {
            echo "Big no! It ain't possible.";
        }
    }

    public function authenticate($password)
    {
        return $password === '$ecr@t';
    }

    public function close()
    {
        $this->door->close();
    }
}

And here is how it can be used

$door = new Security(new LabDoor());
$door->open('invalid'); // Big no! It ain't possible.

$door->open('$ecr@t'); // Opening lab door
$door->close(); // Closing lab door

Un autre exemple serait une sorte d'implémentation de mappeur de données. Par exemple, j'ai récemment créé un ODM (Object Data Mapper) pour MongoDB en utilisant ce modèle où j'ai écrit un proxy autour des classes Mongo tout en utilisant la méthode magique __call(). Tous les appels de méthode ont été proxysés vers la classe Mongo originale et le résultat récupéré a été retourné tel quel, mais dans le cas de find ou findOne, les données ont été mappées vers les objets de la classe requise et l'objet a été retourné à la place de Cursor.

Comportemental Design Patterns

En clair

It is concerned with assignment of responsibilities between the objects. What makes them different from structural patterns is they don't just specify the structure but also outline the patterns for message passing/communication between them. Or in other words, they assist in answering "How to run a behavior in software component?"

D'après Wikipédia

In software engineering, behavioral design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication.

🔗 Chain of Responsibility

Un exemple dans le monde réel

Par exemple, vous avez trois méthodes de paiement (A, B et C) dans votre compte, chacune ayant un montant différent. A a 100 USD, B a 300 USD et C a 1000 USD et la préférence pour les paiements est choisie comme A puis B puis C. Vous essayez d'acheter quelque chose qui vaut 210 USD. En utilisant la Chaîne de responsabilité, on vérifie tout d'abord si le compte A peut effectuer l'achat, si oui l'achat est effectué et la chaîne est rompue. Si ce n'est pas le cas, la requête sera transmise au compte B, qui vérifiera le montant de l'achat. Si c'est le cas, la chaîne sera rompue, sinon la requête continuera d'être transmise jusqu'à ce qu'elle trouve un gestionnaire approprié. Ici, A, B et C sont les maillons de la chaîne et l'ensemble du phénomène est une Chaîne de responsabilité.

En clair

Il permet de construire une chaîne d'objets. La demande entre d'un côté et continue d'aller d'un objet à l'autre jusqu'à ce qu'elle trouve le gestionnaire approprié.

D'après Wikipédia

In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.

Exemple de programme

Translating our account example above. First of all we have a base account having the logic for chaining the accounts together and some accounts

abstract class Account
{
    protected $successor;
    protected $balance;

    public function setNext(Account $account)
    {
        $this->successor = $account;
    }

    public function pay(float $amountToPay)
    {
        if ($this->canPay($amountToPay)) {
            echo sprintf('Paid %s using %s' . PHP_EOL, $amountToPay, get_called_class());
        } elseif ($this->successor) {
            echo sprintf('Cannot pay using %s. Proceeding ..' . PHP_EOL, get_called_class());
            $this->successor->pay($amountToPay);
        } else {
            throw new Exception('None of the accounts have enough balance');
        }
    }

    public function canPay($amount): bool
    {
        return $this->balance >= $amount;
    }
}

class Bank extends Account
{
    protected $balance;

    public function __construct(float $balance)
    {
        $this->balance = $balance;
    }
}

class Paypal extends Account
{
    protected $balance;

    public function __construct(float $balance)
    {
        $this->balance = $balance;
    }
}

class Bitcoin extends Account
{
    protected $balance;

    public function __construct(float $balance)
    {
        $this->balance = $balance;
    }
}

Now let's prepare the chain using the links defined above (i.e. Bank, Paypal, Bitcoin)

// Let's prepare a chain like below
//      $bank->$paypal->$bitcoin
//
// First priority bank
//      If bank can't pay then paypal
//      If paypal can't pay then bit coin

$bank = new Bank(100);          // Bank with balance 100
$paypal = new Paypal(200);      // Paypal with balance 200
$bitcoin = new Bitcoin(300);    // Bitcoin with balance 300

$bank->setNext($paypal);
$paypal->setNext($bitcoin);

// Let's try to pay using the first priority i.e. bank
$bank->pay(259);

// Output will be
// ==============
// Cannot pay using bank. Proceeding ..
// Cannot pay using paypal. Proceeding ..:
// Paid 259 using Bitcoin!

👮 Command

Un exemple dans le monde réel

Un exemple générique serait que vous commandiez de la nourriture au restaurant. Vous (Client) demandez au serveur (Invoker) d'apporter de la nourriture (Command) et le serveur transmet simplement la demande au Chef (Receiver) qui a la connaissance de ce qui doit être cuisiné et comment. Un autre exemple serait que vous (Client) allumant (Command) la télévision (Receiver) à l'aide d'une télécommande (Invoker).

En clair

Permet d'encapsuler des actions dans des objets. L'idée clé de ce modèle est de fournir les moyens de découpler le client du récepteur.

D'après Wikipédia

Dans la programmation orientée objet, le modèle de commande est un modèle de conception comportementale dans lequel un objet est utilisé pour encapsuler toutes les informations nécessaires à l'exécution d'une action ou au déclenchement d'un événement à un moment ultérieur. Ces informations comprennent le nom de la méthode, l'objet qui possède la méthode et les valeurs des paramètres de la méthode.

Exemple de programme

Tout d'abord, nous avons le récepteur qui contient la mise en œuvre de chaque action susceptible d'être effectuée

// Receiver
class Ampoule
{
    public function turnOn()
    {
        echo "Ampoule has been lit";
    }

    public function turnOff()
    {
        echo "Darkness!";
    }
}

puis nous avons une interface que chaque commande va mettre en œuvre et nous avons un ensemble de commandes

interface Command
{
    public function execute();
    public function undo();
    public function redo();
}

// Command
class TurnOn implements Command
{
    protected $ampoule;

    public function __construct(Ampoule $ampoule)
    {
        $this->ampoule = $ampoule;
    }

    public function execute()
    {
        $this->ampoule->turnOn();
    }

    public function undo()
    {
        $this->ampoule->turnOff();
    }

    public function redo()
    {
        $this->execute();
    }
}

class TurnOff implements Command
{
    protected $ampoule;

    public function __construct(Ampoule $ampoule)
    {
        $this->ampoule = $ampoule;
    }

    public function execute()
    {
        $this->ampoule->turnOff();
    }

    public function undo()
    {
        $this->ampoule->turnOn();
    }

    public function redo()
    {
        $this->execute();
    }
}

Ensuite, nous avons un Invoker avec lequel le client va interagir pour traiter les commandes.

// Invoker
class RemoteControl
{
    public function submit(Command $command)
    {
        $command->execute();
    }
}

Enfin, voyons comment nous pouvons l'utiliser dans notre client

$ampoule = new Ampoule();

$turnOn = new TurnOn($ampoule);
$turnOff = new TurnOff($ampoule);

$remote = new RemoteControl();
$remote->submit($turnOn); // Ampoule à été allumée !
$remote->submit($turnOff); // Obscurité !

Le modèle de commande peut également être utilisé pour mettre en œuvre un système basé sur les transactions. Il s'agit de conserver l'historique des commandes dès qu'elles sont exécutées. Si la dernière commande est exécutée avec succès, tout va bien, sinon il suffit de parcourir l'historique et de continuer à exécuter undo sur toutes les commandes exécutées.

➿ Iterator

Un exemple dans le monde réel

Un vieux poste de radio est un bon exemple d'itérateur, où l'utilisateur peut commencer par une chaîne et utiliser les boutons "suivant" ou "précédent" pour passer d'une chaîne à l'autre. Ou prenez l'exemple d'un lecteur MP3 ou d'un téléviseur où vous pouvez appuyer sur les boutons "suivant" et "précédent" pour parcourir les chaînes consécutives. En d'autres termes, ils fournissent tous une interface pour parcourir les chaînes, les chansons ou les stations de radio respectives.

En clair

Il permet d'accéder aux éléments d'un objet sans exposer la présentation sous-jacente.

D'après Wikipédia

In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. The iterator pattern decouples algorithms from containers; in some cases, algorithms are necessarily container-specific and thus cannot be decoupled.

Exemple de programme

In PHP it is quite easy to implement using SPL (Standard PHP Library). Translating our radio stations example from above. First of all we have RadioStation

class RadioStation
{
    protected $frequency;

    public function __construct(float $frequency)
    {
        $this->frequency = $frequency;
    }

    public function getFrequency(): float
    {
        return $this->frequency;
    }
}

Then we have our iterator

use Countable;
use Iterator;

class StationList implements Countable, Iterator
{
    /** @var RadioStation[] $stations */
    protected $stations = [];

    /** @var int $counter */
    protected $counter;

    public function addStation(RadioStation $station)
    {
        $this->stations[] = $station;
    }

    public function removeStation(RadioStation $toRemove)
    {
        $toRemoveFrequency = $toRemove->getFrequency();
        $this->stations = array_filter($this->stations, function (RadioStation $station) use ($toRemoveFrequency) {
            return $station->getFrequency() !== $toRemoveFrequency;
        });
    }

    public function count(): int
    {
        return count($this->stations);
    }

    public function current(): RadioStation
    {
        return $this->stations[$this->counter];
    }

    public function key()
    {
        return $this->counter;
    }

    public function next()
    {
        $this->counter++;
    }

    public function rewind()
    {
        $this->counter = 0;
    }

    public function valid(): bool
    {
        return isset($this->stations[$this->counter]);
    }
}

And then it can be used as

$stationList = new StationList();

$stationList->addStation(new RadioStation(89));
$stationList->addStation(new RadioStation(101));
$stationList->addStation(new RadioStation(102));
$stationList->addStation(new RadioStation(103.2));

foreach($stationList as $station) {
    echo $station->getFrequency() . PHP_EOL;
}

$stationList->removeStation(new RadioStation(89)); // Will remove station 89

👽 Mediator

Un exemple dans le monde réel

Par exemple, lorsque vous parlez à quelqu'un sur votre téléphone portable, un fournisseur de réseau se trouve entre vous et cette personne et votre conversation passe par lui au lieu d'être envoyée directement. Dans ce cas, le fournisseur de réseau est un médiateur.

En clair

Mediator pattern adds a third party object (called mediator) to control the interaction between two objects (called colleagues). It helps reduce the coupling between the classes communicating with each other. Because now they don't need to have the knowledge of each other's implementation.

D'après Wikipédia

In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior.

Exemple de programme

Voici l'exemple le plus simple d'un salon de discussion (c'est-à-dire un médiateur) avec des utilisateurs (c'est-à-dire des collègues) qui s'envoient des messages.

Tout d'abord, nous avons le médiateur, c'est-à-dire le salon de discussion (ChatRoomMediator).

interface ChatRoomMediator 
{
    public function showMessage(User $user, string $message);
}

// Mediator
class ChatRoom implements ChatRoomMediator
{
    public function showMessage(User $user, string $message)
    {
        $time = date('M d, y H:i');
        $sender = $user->getName();

        echo $time . '[' . $sender . ']:' . $message;
    }
}

Ensuite, nous avons nos utilisateurs, c'est-à-dire nos collègues.

class User {
    protected $name;
    protected $chatMediator;

    public function __construct(string $name, ChatRoomMediator $chatMediator) {
        $this->name = $name;
        $this->chatMediator = $chatMediator;
    }

    public function getName() {
        return $this->name;
    }

    public function send($message) {
        $this->chatMediator->showMessage($this, $message);
    }
}

Et l'usage

$mediator = new ChatRoom();

$john = new User('John Doe', $mediator);
$jane = new User('Jane Doe', $mediator);

$john->send('Hi there!');
$jane->send('Hey!');

// Output will be
// Feb 14, 10:58 [John]: Hi there!
// Feb 14, 10:58 [Jane]: Hey!

💾 Memento

Un exemple dans le monde réel

Prenons l'exemple d'une calculatrice (c'est-à-dire l'initiateur), où chaque fois que vous effectuez un calcul, le dernier calcul est sauvegardé en mémoire (c'est-à-dire le mémento) afin que vous puissiez y revenir et peut-être le restaurer à l'aide de boutons d'action (c'est-à-dire le gardien).

En clair

Le modèle Memento consiste à capturer et à stocker l'état actuel d'un objet de manière à ce qu'il puisse être restauré ultérieurement.

D'après Wikipédia

Le modèle de mémento est un modèle de conception logicielle qui permet de restaurer un objet à son état antérieur (annulation par retour en arrière).

Généralement utile lorsque vous devez fournir une sorte de fonctionnalité d'annulation (undo).

Exemple de programme

Prenons l'exemple d'un éditeur de texte qui enregistre l'état de temps en temps et que vous pouvez restaurer si vous le souhaitez.

Tout d'abord, nous avons notre objet memento qui sera capable de contenir l'état de l'éditeur

class EditorMemento
{
    protected $content;

    public function __construct(string $content)
    {
        $this->content = $content;
    }

    public function getContent()
    {
        return $this->content;
    }
}

Nous avons ensuite notre éditeur, c'est-à-dire le créateur, qui va utiliser l'objet memento.

class Editor
{
    protected $content = '';

    public function type(string $words)
    {
        $this->content = $this->content . ' ' . $words;
    }

    public function getContent()
    {
        return $this->content;
    }

    public function save()
    {
        return new EditorMemento($this->content);
    }

    public function restore(EditorMemento $memento)
    {
        $this->content = $memento->getContent();
    }
}

And then it can be used as

$editor = new Editor();

// Type some stuff
$editor->type('This is the first sentence.');
$editor->type('This is second.');

// Save the state to restore to : This is the first sentence. This is second.
$saved = $editor->save();

// Type some more
$editor->type('And this is third.');

// Output: Content before Saving
echo $editor->getContent(); // This is the first sentence. This is second. And this is third.

// Restoring to last saved state
$editor->restore($saved);

$editor->getContent(); // This is the first sentence. This is second.

😎 Observer

Un exemple dans le monde réel

Un bon exemple serait celui des demandeurs d'emploi qui s'inscrivent sur un site d'offres d'emploi et qui sont avertis chaque fois qu'il y a une offre d'emploi correspondante.

En clair

Définit une dépendance entre les objets de sorte que chaque fois qu'un objet change d'état, tous ses dépendants en soient informés.

D'après Wikipédia

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

Exemple de programme

Traduisons notre exemple ci-dessus. Tout d'abord, nous avons des demandeurs d'emploi qui doivent être informés d'une offre d'emploi

class JobPost
{
    protected $title;

    public function __construct(string $title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }
}

class JobSeeker implements Observer
{
    protected $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function onJobPosted(JobPost $job)
    {
        // Do something with the job posting
        echo 'Hi ' . $this->name . '! New job posted: '. $job->getTitle();
    }
}

Then we have our job postings to which the job seekers will subscribe

class JobPostings implements Observable
{
    protected $observers = [];

    protected function notify(JobPost $jobPosting)
    {
        foreach ($this->observers as $observer) {
            $observer->onJobPosted($jobPosting);
        }
    }

    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    public function addJob(JobPost $jobPosting)
    {
        $this->notify($jobPosting);
    }
}

Then it can be used as

// Create subscribers
$johnDoe = new JobSeeker('John Doe');
$janeDoe = new JobSeeker('Jane Doe');

// Create publisher and attach subscribers
$jobPostings = new JobPostings();
$jobPostings->attach($johnDoe);
$jobPostings->attach($janeDoe);

// Add a new job and see if subscribers get notified
$jobPostings->addJob(new JobPost('Software Engineer'));

// Output
// Hi John Doe! New job posted: Software Engineer
// Hi Jane Doe! New job posted: Software Engineer

🏃 Visitor

Un exemple dans le monde réel

Prenons l'exemple d'une personne qui se rend à Dubaï. Il lui suffit de disposer d'un moyen (c'est-à-dire d'un visa) pour entrer à Dubaï. Après son arrivée, elle peut venir visiter n'importe quel endroit de Dubaï sans avoir à demander d'autorisation ou à faire des démarches pour visiter un endroit quelconque ; il suffit de lui indiquer un endroit et elle peut le visiter. C'est exactement ce que vous permet de faire le pattern Visitor qui vous aide à ajouter des lieux à visiter pour qu'ils puissent en visiter le plus possible sans avoir à faire de démarches.

En clair

Le modèle du Visitor permet d'ajouter des opérations supplémentaires aux objets sans avoir à les modifier.

D'après Wikipédia

In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle.

Exemple de programme

Prenons l'exemple d'une simulation de zoo où nous avons plusieurs sortes d'animaux et où nous devons les rendre sains. Traduisons cela en utilisant le modèle de visiteur

// Visitee
interface Animal
{
    public function accept(AnimalOperation $operation);
}

// Visitor
interface AnimalOperation
{
    public function visitMonkey(Monkey $monkey);
    public function visitLion(Lion $lion);
    public function visitDolphin(Dolphin $dolphin);
}

Then we have our implementations for the animals

class Monkey implements Animal
{
    public function shout()
    {
        echo 'Ooh oo aa aa!';
    }

    public function accept(AnimalOperation $operation)
    {
        $operation->visitMonkey($this);
    }
}

class Lion implements Animal
{
    public function roar()
    {
        echo 'Roaaar!';
    }

    public function accept(AnimalOperation $operation)
    {
        $operation->visitLion($this);
    }
}

class Dolphin implements Animal
{
    public function speak()
    {
        echo 'Tuut tuttu tuutt!';
    }

    public function accept(AnimalOperation $operation)
    {
        $operation->visitDolphin($this);
    }
}

Let's implement our visitor

class CryAnimal implements AnimalOperation
{
    public function visitMonkey(Monkey $monkey)
    {
        $monkey->shout();
    }

    public function visitLion(Lion $lion)
    {
        $lion->roar();
    }

    public function visitDolphin(Dolphin $dolphin)
    {
        $dolphin->speak();
    }
}

And then it can be used as

$monkey = new Monkey();     // implements Animal
$lion = new Lion();         // implements Animal
$dolphin = new Dolphin();   // implements Animal

$speak = new CryAnimal();   // implements AnimalOperation

$monkey->accept($speak);    // Ooh oo aa aa!    
$lion->accept($speak);      // Roaaar!
$dolphin->accept($speak);   // Tuut tutt tuutt!

Nous aurions pu le faire simplement en ayant une hiérarchie d'héritage pour les animaux, mais nous aurions alors dû modifier les animaux chaque fois que nous aurions dû ajouter de nouvelles actions aux animaux. Mais maintenant, nous n'aurons plus à les modifier. Par exemple, si l'on nous demande d'ajouter le comportement de saut aux animaux, nous pouvons simplement le faire en créant un nouveau visiteur

class Jump implements AnimalOperation
{
    public function visitMonkey(Monkey $monkey)
    {
        echo 'Jumped 20 feet high! on to the tree!';
    }

    public function visitLion(Lion $lion)
    {
        echo 'Jumped 7 feet! Back on the ground!';
    }

    public function visitDolphin(Dolphin $dolphin)
    {
        echo 'Walked on water a little and disappeared';
    }
}

And for the usage

$jump = new Jump();

$monkey->accept($speak);   // Ooh oo aa aa!
$monkey->accept($jump);    // Jumped 20 feet high! on to the tree!

$lion->accept($speak);     // Roaaar!
$lion->accept($jump);      // Jumped 7 feet! Back on the ground!

$dolphin->accept($speak);  // Tuut tutt tuutt!
$dolphin->accept($jump);   // Walked on water a little and disappeared

💡 Strategy

Un exemple dans le monde réel

Prenons l'exemple du tri : nous avons mis en œuvre le tri par bulles, mais les données ont commencé à croître et le tri par bulles a commencé à devenir très lent. Pour remédier à ce problème, nous avons mis en œuvre le tri rapide. Mais maintenant, bien que l'algorithme de tri rapide soit plus efficace pour les grands ensembles de données, il est très lent pour les petits ensembles de données. Pour remédier à ce problème, nous avons mis en place une stratégie qui consiste à utiliser le tri par bulles pour les petits ensembles de données et le tri rapide pour les plus grands.

En clair

Le modèle de stratégie vous permet de changer d'algorithme ou de stratégie en fonction de la situation.

D'après Wikipédia

En programmation informatique, le pattern Strategy est un modèle de conception de logiciel comportemental qui permet de sélectionner le comportement d'un algorithme au moment de l'exécution.

Exemple de programme

Tout d'abord, nous avons notre interface SortStrategy et différentes implémentations de Strategy

interface SortStrategy
{
    public function sort(array $dataset): array;
}

class BubbleSortStrategy implements SortStrategy
{
    public function sort(array $dataset): array
    {
        echo "tri à l'aide du tri à bulles";

        // Effectuer le tri
        return $dataset;
    }
}

class QuickSortStrategy implements SortStrategy
{
    public function sort(array $dataset): array
    {
        echo "tri à l'aide d'un tri rapide";

        // Effectuer le tri
        return $dataset;
    }
}

Et puis nous avons notre client qui va utiliser n'importe quelle stratégie

class Sorter
{
    protected $sorter;

    public function __construct(SortStrategy $sorter)
    {
        $this->sorter = $sorter;
    }

    public function sort(array $dataset): array
    {
        return $this->sorter->sort($dataset);
    }
}

Et il peut être utilisé comme

$dataset = [1, 5, 4, 3, 2, 8];

// class BubbleSortStrategy implements SortStrategy
$sorter = new Sorter(new BubbleSortStrategy());
$sorter->sort($dataset); // Résultat : tri à l'aide du tri à bulles

// class QuickSortStrategy implements SortStrategy
$sorter = new Sorter(new QuickSortStrategy());
$sorter->sort($dataset); // Résultat : tri à l'aide d'un tri rapide

💢 State

Un exemple dans le monde réel

Imaginez que vous utilisiez une application de dessin et que vous choisissiez le pinceau pour dessiner. Maintenant, le pinceau change de comportement en fonction de la couleur sélectionnée, c'est-à-dire que si vous avez choisi la couleur rouge, il dessinera en rouge, s'il s'agit du bleu, il sera en bleu, etc.

En clair

Il vous permet de modifier le comportement d'une classe lorsque l'état change.

D'après Wikipédia

Le pattern State est un modèle de conception de logiciel comportemental qui implémente une machine d'état (state machine) d'une manière orientée objet. Avec le pattern State, une machine à états est implémentée en implémentant chaque état individuel comme une classe dérivée de l'interface du pattern State, et en implémentant les transitions d'état en invoquant les méthodes définies par la superclasse du modèle. Le pattern State peut être interprété comme un modèle de stratégie capable de changer la stratégie en cours par l'invocation de méthodes définies dans l'interface du modèle.

Exemple de programme

Prenons l'exemple de l'éditeur de texte, il vous permet de modifier l'état du texte qui est tapé, c'est-à-dire que si vous avez sélectionné gras, il commence à écrire en gras, s'il est en italique, il commence à écrire en italique, etc.

Tout d'abord, nous avons notre interface WritingState et quelques implémentations d'état

interface WritingState
{
    public function write(string $words);
}

class UpperCase implements WritingState
{
    public function write(string $words)
    {
        echo strtoupper($words);
    }
}

class LowerCase implements WritingState
{
    public function write(string $words)
    {
        echo strtolower($words);
    }
}

class DefaultWritingState implements WritingState
{
    public function write(string $words)
    {
        echo $words;
    }
}

Then we have our editor

class TextEditor
{
    protected $state;

    public function __construct(WritingState $state)
    {
        $this->state = $state;
    }

    public function setState(WritingState $state)
    {
        $this->state = $state;
    }

    public function type(string $words)
    {
        $this->state->write($words);
    }
}

And then it can be used as

$editor = new TextEditor(new DefaultWritingState());

$editor->type('First line');

$editor->setState(new UpperCase());

$editor->type('Second line');
$editor->type('Third line');

$editor->setState(new LowerCase());

$editor->type('Fourth line');
$editor->type('Fifth line');

// Output:
// First line
// SECOND LINE
// THIRD LINE
// fourth line
// fifth line

📒 Template Method

Un exemple dans le monde réel

Supposons que nous fassions construire une maison. Les étapes de la construction pourraient être les suivantes

  • Préparer la base de la maison
  • Construire les murs
  • Ajouter le toit
  • Ajouter les autres étages

L'ordre de ces étapes ne peut jamais être modifié, c'est-à-dire que l'on ne peut pas construire le toit avant de construire les murs, etc., mais chacune des étapes peut être modifiée, par exemple les murs peuvent être en bois, en polyester ou en pierre.

En clair

La méthode des modèles définit le squelette de la manière dont un certain algorithme pourrait être exécuté, mais reporte la mise en œuvre de ces étapes sur les classes enfants.

D'après Wikipédia

En génie logiciel, le modèle de méthode template est un modèle de conception comportementale qui définit le squelette du programme d'un algorithme dans une opération, en reportant certaines étapes à des sous-classes. Il permet de redéfinir certaines étapes d'un algorithme sans en modifier la structure.

Exemple de programme

Imaginons que nous ayons un outil de construction qui nous aide à tester, à lister, à construire, à générer des rapports de construction (c'est-à-dire des rapports de couverture de code, des rapports de linting, etc.

Tout d'abord, nous avons notre classe de base qui spécifie le squelette de l'algorithme de construction

abstract class Builder
{

    // Template method
    final public function build()
    {
        $this->test();
        $this->lint();
        $this->assemble();
        $this->deploy();
    }

    abstract public function test();
    abstract public function lint();
    abstract public function assemble();
    abstract public function deploy();
}

Then we can have our implementations

class AndroidBuilder extends Builder
{
    public function test()
    {
        echo 'Exécuter les tests android';
    }

    public function lint()
    {
        echo 'Linting du code android';
    }

    public function assemble()
    {
        echo 'Assembler le build android';
    }

    public function deploy()
    {
        echo 'Déploiement d\'une version d\'Android sur le serveur';
    }
}

class IosBuilder extends Builder
{
    public function test()
    {
        echo 'Exécuter les tests ios';
    }

    public function lint()
    {
        echo 'Linting the ios code';
    }

    public function assemble()
    {
        echo 'Assembler la compilation ios';
    }

    public function deploy()
    {
        echo 'Déploiement d\'une version d\'ios sur le serveur';
    }
}

And then it can be used as

$androidBuilder = new AndroidBuilder();
$androidBuilder->build();

// Sortie :
// Exécution des tests android
// Linting du code android
// Assemblage du code android
//'Déploiement d'une version d'Android sur le serveur

$iosBuilder = new IosBuilder();
$iosBuilder->build();

// Sortie :
// Exécution des tests ios
// Linting du code ios
// Assemblage de la version d'ios
// Déploiement d'une version d'ios sur le serveur

License

MIT © Kamran Ahmed

Releases

No releases published

Packages

No packages published