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

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *