Le Design Pattern Fabrique (Factory) en PHP

La fabrique (Factory en anglais) est avec le Singleton l’un des Design Patterns les plus aisé à comprendre et à mettre en œuvre.

Quelle problématique ?

L’objectif du modèle de conception Fabrique est de fournir un objet prêt à l’emploi, configuré correctement, en libérant le code client de toute responsabilité (choix de l’implémentation, configuration, instanciation, …).

Nous verrons au travers des exemples suivants que ce ne sont pas de simples constructeurs.

Dans quels cas ?

Le modèle de conception Fabrique est intéressant dans les cas suivants :

  • Vous avez plusieurs objets respectant la même interface ou héritant de la même classe abstraite, mais chacun adapté à un contexte différent (par exemple une interface IDB avec une classe par base de données DBMySql, DBOci, DBPgSql, …)
  • Vous voulez centraliser le code en charge du choix de l’objet à créer

Exposé de l’exemple

Supposons que vous avez créé l’interface IDB suivante :

interface IDB {
/**
* Connecte l'objet à la base de données
*/
public function connect ($connectionString);
/**
* Exécute une requête à la base de données
*/
public function doQuery ($queryString, $arBinds);
//.... suivent d'autres méthodes inutiles dans l'exemple
}

Dans votre application, vous utilisez plusieurs types de base de données, des bases MySql et des bases Oracles. De ce fait, vous développez deux classes spécifiques pour ces bases, respectant l’interface IDB.

class DBMysql implements IDB {
//Implémentation spécifique à MySql
}
class DBOracle implements IDB {
//Implémentation spécifique à Oracle
}

Utilisation des objets sans Fabrique

Si vous n’utilisez pas de fabrique, chaque fois que vous allez vouloir instancier une connexion, vous allez devoir choisir vous même l’objet à instancier.

$db = new DBMysql ('mysql://user:password@localhost');
//plus loin
$db2 = new DBOracle ('oracle://user:password@localhost')

L’inconvénient est clair : demain, si vous changez de type de base pour une partie de votre code, vous devrez convertir les instanciations de DBOracle vers DBMysql ou vice versa.

Pour éviter cela, vous pouvez aussi procéder de la sorte :

//quelquepart dans le code, on définit le paramètre de connexion
$connectionString1 = 'mysql://user:password@localhost';

//plus loin (en supposant que la chaine de connexion est valide)
switch (substr ($connectionString1, 0, strpos ($connectionString1, ':'))){
   case 'mysql':
      $db = new DBMySql ($connectionString1);
      break;
   case 'oracle':
      $db = new DBOracle ($connectionString1);
      break;
   default:
      throw new Exception ('Type de base inconnu');
}

Avec cette solution vous développez la logique de choix de l’implémentation dans votre code client. Si l’avantage est de vous permettre d’appréhender le possible changement de base de données, il reste plusieurs inconvénients :

  • Vous allez copier / coller le code concerné de partout ou vous souhaiterez créer une connexion.
  • Si demain vous voulez intégrer un nouveau type de base de données, vous devrez modifier ce code de partout ou vous l’avez dupliqué.

La solution avec le modèle de conception fabrique

Utilisons maintenant la fameuse fabrique, avec le code suivant

class DBFactory {
   public static function create ($connectionString){
      if (($driverEndPos = strpos ($connectionString, ':')) === false){
         throw new Exception ('Mauvaise chaine de connexion');
      }

      switch (substr ($connectionString, 0, $driverEndPos)){
         case 'mysql':
            $db = new DBMySql ($connectionString1);
            break;
         case 'oracle':
            $db = new DBOracle ($connectionString1);
            break;
         default:
            throw new Exception ('Type de base inconnu');
      }
      return $db;
   }
}

Notre code client précédent est grandement allégé, avec par exemple :

$db = DBFactory::create ('mysql://user:password@localhost');
//plus loin
$db2 = DBFactory::create ('oracle://user:password@localhost')

De plus, si demain un nouveau type de base de données est ajouté, seule la fabrique sera à adapter.

Autres avantages

La fabrique est seule responsable de la création / distribution de l’objet, ainsi, vous pouvez décider de réaliser des opérations complémentaires, à savoir :

  • préparer l’encodage (UTF8, …)
  • indiquer que vous souhaitez encapsuler les requêtes dans des transactions
  • ne distribuer que une seule instance de l’objet (un peu comme le singleton, mais géré par la fabrique et non par l’objet en lui même)
  • Réaliser des opérations de log
  • Intégrer encode plus de logique de création dans la fabrique

Variantes

Il existe deux types de fabriques :

  • Les Fabriques dont nous venons de parler, qui sont des classes dont la finalité est de créer d’autres objets
  • Les méthodes de fabrique (factory method) qui sont des méthodes de « vrais objets » dédiées à la création d’objet.
  1. L’Adaptateur (Adaptater) en PHP | Gerald's Blog - pingback on 7 décembre 2010 at 8 h 55 min
  2. La Stratégie (Strategy) en PHP | Gerald's Blog - pingback on 29 décembre 2010 at 14 h 43 min
  3. Singleton, Multiton et Alternatives en PHP | Gerald's Blog - pingback on 19 janvier 2011 at 12 h 51 min
  4. pierre-emmanuel

    public static function create ($connectionString){}

    il manque juste un return $db
    l’exemple actuel ne fonctinne pas.
    $db et $db2 n’ont pas de références à l’objet créé et sont donc « nuls »
    ;)

  5. Merci d’avoir relevé la coquille, j’ai corrigé l’exemple.

  6. Le Poid Mouche (Flyweight) en PHP | Gerald's Blog - pingback on 9 juin 2011 at 10 h 52 min
  7. Ton blog est vraiment bien !

    J’avais vraiment envie de comprendre comment ça marche et c’est fait ! ^^

    Merci

  8. Salut,

    Une autre petite coquille s’est glissée dans le code :

    Supposons que vous avez créée l’interface IDB suivante :

    (Sans compter la faute d’orthographe à « créé »)

    Alors que dans le code on lit :

    class IDB {

    Et non :

    interface IDB {

    Difficile d’implémenter une classe .. :wink:

    class DBMysql implements IDB {

  9. Bonjour,

    du fait que maintenant en php, nous pouvons faire ceci :

    $driverEndPos = strpos ($connectionString, ':')
    $class = substr ($connectionString, 0, $driverEndPos); // pour reprendre ton exemple
    new $class();

    j’avoue que je ne vois plus très bien l’utilité de ce pattern.
    Quelqu’un pourrait’il m’éclairer ?

    Cordialement

    erwan

    • Bonjour.

      L’intérêt de la factory ne réside pas dans la simple création de l’objet, mais dans l’encapsulation de la logique de création.

      Ainsi, ce que tu proposes peut être une forme d’implémentation de factory.

      Toutefois, ce n’est pas au code client de connaitre la logique de création que tu cites, mais bien à un tiers.

  10. De l’usage des méthodes statiques | Gerald's Blog - pingback on 8 janvier 2013 at 7 h 43 min
  11. Merci pour ce billet très intéressant :smile:

    Une petite erreur :
    Intégrer encode plus de logique de création dans la fabrique

Laisser un commentaire


NOTE - Vous pouvez utiliser les éléments et attributs HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>