Archives de catégorie : Design Patterns

PHP : le design pattern Adaptateur

Chez Auto Load, tout est permis !

Pour illustrer notre exemple du design pattern Adaptateur, nous allons cette fois nous prendre pour des inspecteurs du permis de conduire ! Chez AUTO LOAD, nous nous vantons d’obtenir un taux de réussite de 99% à l’examen du permis de conduire et ce sans soudoyer de quelque manière que ce soit nos candidats ! Voyons sans tarder les principales classes qui composent notre exemple. Tout d’abord, la classe InspecteurPermisConduire, qui est notre classe cliente. C’est elle qui va faire usage de nos autres classes.

class InspecteurPermisConduire {
    private $_candidat;
	
    public function __construct(ConducteurInterface $conducteur)
    {
        $this->_candidat = $conducteur;
    }
    public function changerCandidat(ConducteurInterface $conducteur)
    {
        $this->_candidat = $conducteur;
    }
    public function fairePasserExamen()
    {
        $this->_candidat->demarrer();
        $this->_candidat->accelerer();
        $this->_candidat->tournerDroite();
        $this->_candidat->accelerer();
        $this->_candidat->tournerGauche();
        $this->_candidat->ralentir();
        $this->_candidat->reculer();
        $this->_candidat->immobiliser();
    }
}

Une instance donnée d’InspecteurPermisConduire prend en composition tout objet se conformant à l’interface ConducteurInterface : tout d’abord, elle l’exige dans son constructeur, c’est à dire qu’aucune instance d’InspecteurPermisConduire ne peut exister sans un objet qui implémente en totalité ConducteurInterface. Ensuite, nous nous réservons la possibilité de changer de type de candidat à l’exécution (runtime) grâce à un setter. Enfin, la valeur ajoutée de notre classe réside dans sa méthode fairePasserExamen, qui fait effectuer à tout conducteur les manœuvres de base, indispensables pour décrocher le précieux sésame !

Voici justement notre Automobiliste :

interface ConducteurInterface {
	public function demarrer();
	public function tournerGauche();
	public function tournerDroite();
	public function accelerer();
	public function ralentir();
	public function reculer();
	public function immobiliser();
}

class Automobiliste implements ConducteurInterface {
	public function demarrer() {
		echo "tourner la clé de contact ou mettre la carte";
	}
	public function tournerGauche() {
		echo "tourner le volant vers la gauche";
	}
	public function tournerDroite() {
		echo "tourner le volant vers la droite";
	}
	public function accelerer() {
		echo "appuyer sur la pédale d'accélération";
	}
	public function ralentir() {
		echo "relâcher la pédale d'accélération et/ou", 
                      "appuyer sur la pédale de frein";
	}
	public function reculer() {
		echo "passer la marche arrière et accélérer";
	}
	public function immobiliser() {
		echo "mettre le frein à main";
	}
}

Notre classe concrète Automobiliste implémente les fonctions imposées par l’interface ConducteurInterface. Cette interface recense les principales manoeuvres dont la maîtrise est exigée de tout conducteur de véhicule qui se respecte : démarrer et arrêter le véhicule, avancer, reculer, tourner, accélérer et ralentir. Attention cependant, certains véhicules – comme un vélo – ne se démarrent pas, il va falloir prendre en compte cet aspect là…

Bref, à ce stade là, tout est pour le mieux dans le meilleur des mondes ! [Candide]

Schéma UML de l'exemple de design pattern adaptateur

Dès que le vent soufflera, je recodera… (air bien connu)

Jusqu’au jour où Otto Hekohl, le propriétaire de notre école de conduite, vient nous annoncer qu’étant donné que nous sommes situés en bord de mer, il serait plutôt judicieux – en plus d’être diablement bénéfique pour notre chiffre d’affaire – que nous puissions faire passer le permis bateau à des candidats qui sont de plus en plus nombreux et demandeurs…

Photos de gens sautant d'un yacht

Elle est parfois dure la vie à bord d’un frêle esquif !

Avant de se faire congédier par notre patron pour avoir regardé trop de vidéos de chatons montant sur le dos de tortues durant ses heures de travail, Jojo Dingo, le deuxième programmeur de l’entreprise, avait envisagé d’intégrer ce changement de notre stratégie commerciale. Voici la structure logicielle dont nous héritons, maintenant que Jojo a toutes ses journées de libre pour s’adonner à sa félinophilie maladive :

interface NavigateurInterface {
	public function demarrer();
	public function reculer();
	public function tournerBabord();
	public function tournerTribord();
	public function accelerer();
	public function ralentir();
	public function jeterAncre();
}

abstract class Marin implements NavigateurInterface {
	public function jeterAncre() {
		echo "jeter l'ancre à la mer";
	}
}

class UnsupportedMethodException extends Exception {}

class MarinVoile extends Marin {
	public function demarrer() {
		throw new UnsupportedMethodException
                     ("Cette fonctionnalité n'est pas disponible");
	}
	public function tournerBabord() {
		echo "diriger les voiles et la barre pour aller à babord";
	}
	public function tournerTribord() {
		echo "diriger les voiles et la barre pour aller à tribord";
	}
	public function accelerer() {
		echo "positionner les voiles et déterminer l'allure";
	}
	public function ralentir() {
		echo "positionner les voiles et déterminer l'allure";
	}
	public function reculer() {
		echo "positionner les voiles et manœuvrer pour reculer";
	}
}

class MarinMoteur extends Marin {
	public function demarrer() {
		echo "démarrer le moteur";
	}
	public function tournerBabord() {
		echo "manoeuvrer la barre ou le volant pour aller à babord";
	}
	public function tournerTribord() {
		echo "manoeuvrer la barre ou le volant pour aller à tribord";
	}
	public function accelerer() {
		echo "augmenter la vitesse du moteur";
	}
	public function ralentir() {
		echo "dimininuer la vitesse du moteur ou le couper";
	}
	public function reculer() {
		echo "passer la marche arrière";
	}
}

Jojo a bien compris qu’il nous fallait des classes qui nous permettent de gérer les gens qui s’adonnent à la navigation de plaisance motorisée ou à voile et qu’un bateau ne se manœuvrait pas comme une voiture.

Attardons-nous sur la structure de ces classes : nous avons une classe abstraite Marin qui implémente une interface NavigateurInterface et dont les deux filles concrètes MarinVoile et MarinMoteur réalisent la majeure partie des fonctions.

Schéma UML du design pattern adaptateur

Marin est abstraite car trop générique, elle réalise cependant l’implémentation de jeterAncre, qui est commune à toute forme de navigation (bon, c’est vrai, les Optimist n’ont pas d’ancre). Jojo a choisi de créer une exception pour gérer le cas où la fonction démarrer n’est pas prise en charge, plutôt que de l’isoler dans une interface NavigateurMoteurInterface dédiée aux bateaux qui ont ce mode de propulsion. C’est un choix de conception, qui a le mérite de favoriser la généricité, inutile de faire une vérification de la présence de cette méthode en utilisant par exemple method_exists dans la classe client InspecteurPermisConduire. C’est la classe concrète qui gère l’absence de cette méthode en levant une exception correctement typée.

Comment ne pas se faire mener en bateau ?

Adeptes de longue date de la programmation orientée objet, nous souhaitons minimiser l’impact de ce changement d’architecture logicielle dans nos classes existantes. C’est là où l’adaptateur entre en jeu !

L’adaptateur fonctionne exactement sur le même principe que l’adaptateur de votre sèche-cheveux quand vous allez, en Irlande, par exemple ! Il prend en entrée la prise française de votre sèche-cheveux favori et l’adapte aux prises à trois broches que vous trouvez sur l’île Émeraude ! Vous n’avez pas eu à dénuder les fils de votre appareil pour y fixer une prise irlandaise, et heureusement : l’adaptateur fait le travail pour vous ! Et vous n’y avez vu que du bleu !

S’adapter…au changement

L’adaptateur a pour but de faire collaborer des interfaces de prime abord incompatibles, comme ConducteurInterface et NavigateurInterface. C’est un intermédiaire précieux (et réutilisable, ce qui nous met en joie !) qui va nous éviter de déstabiliser notre code base et qui va découpler le client (notre inspecteur du permis de conduire) de notre adapté (ici, nos marins). Plus le couplage est lâche, mieux c’est, je ne vous apprends rien !

L’adaptateur prend en composition l’adapté, et il sera chargé d’effectuer la translation entre les demandes à destination de l’interface originelle (ConducteurInterface) et la nouvelle interface cible (NavigateurInterface).

Pour que cette translation s’effectue de manière transparente pour le système, il faut naturellement que l’adaptateur implémente l’interface originelle. Regardez donc :

class AdaptateurMarin implements ConducteurInterface {
	private $_marin;
	
	public function __construct(Marin $marin) {
		$this->_marin = $marin;
	}
	public function demarrer() {
		$this->_marin->demarrer();
	}
	public function tournerGauche() {
		$this->_marin->tournerBabord();
	}
	public function tournerDroite() {
		$this->_marin->tournerTribord();
	}
	public function accelerer() {
		$this->_marin->accelerer();
	}
	public function ralentir() {
		$this->_marin->ralentir();
	}
	public function reculer() {
		$this->_marin->reculer();
	}
	public function immobiliser() {
		$this->_marin->jeterAncre();
	}
}

Le polymorphisme avant tout ! Notre adaptateur prend en charge des objets de la classe Marin, c’est à dire potentiellement toutes les sous-classes qui en dérivent. Tant mieux car nous en avons deux : MarinVoile et MarinMoteur !

Nous n’avons pas du tout modifié notre classe existante, nous avons isolé la nouveauté dans une nouvelle classe, autonome, avec des responsabilités clairement identifiées.

Les comportements que vous isolez dans des unités de code indépendantes sont réutilisables, ceux que vous enfermez dans des classes ne le sont pas forcément !

Pour que le système prenne en compte cette nouvelle possibilité (à nous l’argent facile !), il nous faudra juste modifier le code appelant. Notre client avait déjà un modificateur d’implémenté, à la bonne heure, servons nous en donc !

$adaptateur = new AdaptateurMarin(new MarinMoteur());
$inspecteur->changerCandidat($adaptateur);
$inspecteur->fairePasserExamen();

La méthode changerCandidat exige en paramètre une implémentation de la classe ConducteurInterface, ça tombe bien c’est précisément ce qu’est Marin et ses filles concrètes.

Souvenez vous toutefois qu’une exception est levée dans notre adaptateur MarinVoile, il faut donc en réalité modifier quelque peu le code appelant, qui va devoir les intercepter et les traiter le cas échéant. N’allons pas chercher midi à quatorze heures, nous nous contenterons d’en afficher le message. « KISS » is the law !

try {
	$inspecteur->fairePasserExamen();
} catch (UnsupportedMethodException $exception) {
	echo $exception->getMessage();
}

Voici maintenant l’architecture de notre code, avec l’adaptateur qui est venu s’intercaler entre l’existant et nos nouvelles fonctionnalités, qui vont nous permettre de gérer marins d’eau douce, flibustiers, jeunes loups de mer, moules à gaufres et autres ectoplasmes !

schema2

Pour lire cet article dans des conditions optimales, il est fortement conseillé d’écouter autant de fois que nécessaire ce titre (en plus d’aller les voir en concert en mai 2014) :

PHP : un exemple simple du design pattern Template Method

Vous cherchiez un design pattern facile à aborder ? Le design pattern template method est celui qu’il vous faut ! Son principe est très simple : dans une classe, une méthode dite template est composée de sous-méthodes dont on sait que chaque sous-classe l’implémentera à sa manière. Ces sous-méthodes sont généralement en type d’accès protégé car invoquées uniquement par cette fameuse méthode template; l’extérieur n’a pas à connaître les mystères de votre implémentation (encapsulation, vous dîtes ?). Bien entendu, étant donné que chaque classe fille implémentera ces méthodes comme bon lui semble, il convient de les signifier comme abstraites dans la classe mère.

Imaginons une classe TunnelCommande qui expose une méthode template nommée finaliserCommande; cette méthode décrit un algorithme en spécifiant ce qui devra être fait par ses sous-classes et dans quel ordre. Cette classe comporte la méthode payePort dite « adaptateur » (hook) qui peut être réécrite dans les classes filles. Elle sert à conditionner une partie du flot d’exécution de l’algorithme de la méthode template. Son utilité dans notre cas est de permettre à un certain type d’utilisateur de s’affranchir du paiement des frais de port.

abstract class TunnelCommande
{
    public function finaliserCommande(): void
    {
        $this->faireTotal();
        
        if ($this->payePort()) {
            $this->ajouterFraisPort();
        }

        $this->rediriger('page_paiement');
    }

    public function payePort(): bool
    {
    	return true;
    }

    public function rediriger(string $template): void
    {
    	echo "Redirection vers ", $template, PHP_EOL;
    }

    abstract protected function faireTotal(): void;
    abstract protected function ajouterFraisPort(): void;
}

Nous avons deux classes concrètes qui dérivent TunnelCommande et implémentent les méthodes abstraites en leur donnant un comportement spécifique. Dans CommandePremium, la méthode ajouterFraisPort qui est imposée par la classe mère abstraite ne fait rien, payePort renvoyant false elle ne sera de toutes façons jamais invoquée dans ce scénario là.


class CommandeClient extends TunnelCommande
{
    protected function faireTotal(): void
    {
        echo "Je fais le total", PHP_EOL;
    }

    protected function ajouterFraisPort(): void 
    {
        echo "J'applique les frais de port du client normal", PHP_EOL;
    }
}

class CommandePremium extends TunnelCommande
{
    protected function faireTotal(): void
    {
        echo "Appliquer 5% de rabais pour les clients Premium", PHP_EOL;
    }

    protected function ajouterFraisPort(): void
    {
        return;
    }

    public function payePort(): bool
    {
    	return false;
    }
}

$premium = new CommandePremium;
$premium->finaliserCommande();

$standard = new CommandeClient;
$standard->finaliserCommande();

Dans ce design pattern, tout le travail est fait dans la classe mère, abstraite. Quand je dis « tout le travail », je parle de la structure générale de l’algorithme, de l’ordre des opérations. Evidemment, la responsabilité de l’implémentation des détails de cet algorithme « général » est déléguée aux classes dérivées, via le mécanisme d’abstraction.

PHP : un exemple simple de design pattern Decorator

Le design pattern Decorator (en français, décorateur) a pour but d’étendre les fonctionnalités d’un objet grâce à l’utilisation de l’héritage. Mon père m’a toujours dit que d’un âne, on ne pouvait pas faire un cheval de course; je vais m’employer à lui donner tort ! Voici un diagramme de classe qui sert de base à notre exemple, je l’ai réalisé avec l’outil ArgoUML sur GNU/LInux Debian :

Diagrammedeclasses

Au sommet de notre diagramme trône fièrement la classe abstraite Equide : elle possède une variable d’instance protégée de type chaîne de caractères qui stocke une description très sommaire de l’équidé ainsi que deux méthodes dont une (donne Description) est abstraite. Voici son code en détail :


abstract class Equide {
    protected $_description = 'équidé commun';
    abstract public function courir();

    public function donneDescription() {
        return $this->_description;
    }
}

Ce super-type est dérivé en deux classes concrètes :


class AneSauvage extends Equide {
    public function __construct() {
        $this->_description = 'âne sauvage';
    }

    public function courir() {
        echo "Il m'arrive de courir à l'occasion...";
    }
}

class AneDomestique extends Equide {
    public function __construct() {
        $this->_description = 'âne domestique';
    }

    public function courir() {
        echo "Si vraiment on m'y oblige, je trotte...";
    }
}

La méthode courir, signalée abstraite dans la mère est implémentée dans les filles et la valeur par défaut stockée dans la variable d’instance _description est écrasée avec une valeur un peu plus censée lors de la construction de l’objet.

La nouveauté arrive maintenant…


abstract class DecorateurEquide extends Equide {
    protected $_equide;

    public function __construct(Equide $equide) {
        $this->_equide = $equide;
    }
}

class ChevalDeCourse extends DecorateurEquide {

    public function donneDescription() {
        return $this->_equide->donneDescription() .
               ' qui court très très vite !' . PHP_EOL;
    }

    public function courir() {
        return $this->_equide->courir() .
               ' et maintenant je galope tel un cheval de course !' .
               PHP_EOL;
    }
}

Notre classe décorateur est DecorateurEquide; voyez-le par vous-mêmes, c’est une classe abstraite…il faudra donc la dériver. Cette classe prend en composition un objet de la classe Equide, puisque c’est précisément cet objet qu’elle va décorer ! C’est lors de l’instanciation d’une de ses classes filles que l’on passera notre instance d‘Equide au constructeur.

La seule classe fille d’Equide dans notre exemple est ChevalDeCourse; elle ajoute des fonctionnalités aux méthodes courir et donneDescription qui proviennent de DecorateurEquide et donc de Equide. Dans ce cet exemple trivial ces fonctionnalités se résument à une simple chaîne de caractères.

Les décorateurs ont le même type que les objets qu’ils décorent, c’est la raison pour laquelle DecorateurEquide hérite d’Equide, pour en être un sous-type ! L’héritage n’est pas réalisé à des fins « comportementales » mais simplement pour des raisons de typage. J’ai mis la variable d’instance _equide et le constructeur dans DecorateurEquide mais j’aurais pu tout aussi bien laisser cette classe vide (ne m’en servir vraiment que pour le typage) et déporter ce code dans les classes concrètes qui spécialisent (et spécialiseront à terme) DecorateurEquide. C’est un choix de conception parmi d’autres…

Pour utiliser ce code :


$aneDomestique = new AneDomestique;
$cheval = new ChevalDeCourse($aneDomestique);

echo $cheval->donneDescription();
echo $cheval->courir();

Tu vois Papa, c’est bien la preuve que d’un âne, on peut faire un cheval de course !

smiling donkey

Source : blog Terapias Naturales

Le design pattern Factory Method en PHP

Le design pattern Factory Method est appelé en français fabrique; c’est un design pattern dit « de création » puisque le but de Factory Method est de créer des objets. La fabrique est utilisée pour réduire le couplage entre les classes; son but est qu’une classe cliente ne fasse plus des instanciations elle-même mais qu’elle passe par une autre classe qui connait le processus (potentiellement complexe) de la création d’un objet dans les détails.

Ouvrières chargées d'instancier des objets dans une factory method

Ouvrières chargées d’instancier des objets

Présentons la facture !

Soit une classe Facturation exposant une méthode declencher et qui constitue une facture en créant une entête, un corps et un pied de page sur lesquels elle va invoquer la méthode formater.

interface RubriqueInterface
{
    public function formater(): void; 
}

class Entete implements RubriqueInterface
{
    public function formater(): void
    {
        echo "Je formate mon entête".PHP_EOL;
    }
}

class Corps implements RubriqueInterface
{
    private $produits;

    public function __construct(array $produits)
    {
        $this->produits = $produits;
    }

    public function formater(): void
    {
        echo "Corps avec ".count($this->produits)." produits".PHP_EOL;
    }
}

class PiedPage implements RubriqueInterface
{
    public function formater(): void
    {
        echo "Je formate mon pied de page".PHP_EOL;
    }
}

class Facturation
{
    private $entete;

    private $corps;

    private $piedPage;

    public function __construct(array $produits)
    {
        $this->entete = new Entete();
        $this->corps = new Corps($produits);
        $this->piedPage = new PiedPage();
    }

    public function declencher(): void
    {
        $this->entete->formater();
        $this->corps->formater();
        $this->piedPage->formater();
    }
}

$facture = new Facturation([['nom' => 'Gourde', 'prix' => 9.99]]);
$facture->declencher();

Ici Facturation est très fortement couplée avec Entete, Corps et PiedPage puisque les instanciations sont faites directement par cette classe. Nous allons diminuer ce couplage en faisant appel à nos fabriques et leur factory methods. La factory method de notre fabrique abstraite est fabriquer (comme c’était prévisible…):

abstract class FabriqueFacture {
    abstract public function fabriquer();
}

class Facturation
{
    private $entete;

    private $corps;

    private $piedPage;

    public function __construct(array $produits)
    {
        $fabriqueEntete = new FabriqueEnteteFacture();
        $this->entete = $fabriqueEntete->fabriquer();

        $fabriqueCorps = new FabriqueCorpsFacture($produits);
        $this->corps = $fabriqueCorps->fabriquer();

        $fabriquePiedPage = new FabriquePiedPageFacture();
        $this->piedPage = $fabriquePiedPage->fabriquer();
    }

    public function declencher(): void
    {
        $this->entete->formater();
        $this->corps->formater();
        $this->piedPage->formater();   
    }
}

$facture = new Facturation([['nom' => 'Gourde', 'prix' => 9.99]]);
$facture->declencher();
 
class FabriqueEnteteFacture extends FabriqueFacture
{
    private $classeCible = 'Entete';

    public function fabriquer(): RubriqueInterface
    {
        return new $this->classeCible();
    }
}
 
class FabriqueCorpsFacture extends FabriqueFacture
{
    private $classeCible = 'Corps';

    private $produitsAFacturer;

    public function __construct(array $produits)
    {
        $this->produitsAFacturer = $produits;
    }

    public function fabriquer(): RubriqueInterface
    {
        return new $this->classeCible($this->produitsAFacturer);
    }
}
 
class FabriquePiedPageFacture extends FabriqueFacture
{
    private $classeCible = 'PiedPage';

    public function fabriquer(): RubriqueInterface
    {
        return new $this->classeCible();
    }
}

Alors oui, vous allez me dire « Oui mais, elle instancie des fabriques notre classe Facturation » et je ne le contesterai pas ! Facturation fait effectivement appel à des fabriques à qui elle délègue les instanciations. C’est là que réside le découplage !

Ce sont les classes concrètes dérivant FabriqueFacture qui auront la responsabilité de l’instanciation des classes qui constituent les différentes parties d’une facture : une fabrique de pieds de pages sait qu’elle doit instancier des pieds de pages. Souvenez-vous : une classe = une responsabilité ! Afin de vous faciliter la vie durant les tests unitaires, le mieux serait d’injecter directement ses dépendances au constructeur de Facturation, à savoir les 3 fabriques. C’est très simple.

class Facturation
{
    private $entete;

    private $corps;

    private $piedPage;

    public function __construct(FabriqueFacture $fabriqueEntete, 
        FabriqueFacture $fabriqueCorps,
        FabriqueFacture $fabriquePiedPage)
    {
        $this->entete = $fabriqueEntete->fabriquer();
        $this->corps = $fabriqueCorps->fabriquer();
        $this->piedPage = $fabriquePiedPage->fabriquer();
    }

    public function declencher(): void
    {
        $this->entete->formater();
        $this->corps->formater();
        $this->piedPage->formater();
    }
}

$produits = [['nom' => 'Gourde', 'prix' => 9.99]];

$facture = new Facturation(
  new FabriqueEnteteFacture(),
  new FabriqueCorpsFacture($produits),
  new FabriquePiedPageFacture()
);
$facture->declencher();

Résumons les unités de code en présence dans le design pattern Factory Method :

  • La classe cliente : Facturation
  • La fabrique abstraite : FabriqueFacture (notez qu’une interface aurait suffi, essayez !)
  • Les fabriques concrètes : FabriqueEnteteFacture, FabriqueCorpsFacture, FabriquePiedPageFacture
  • Les classes « produits » (ce qu’on veut obtenir au final) : Corps, Entete, PiedPage