Design pattern : Singleton

Design pattern : Singleton

Les objets peuvent généralement agir de façon responsable en effectuant leur travail sur leurs propres attributs, sans avoir d’autre obligation que d’assurer leur cohérence propre. Cependant, certains objets ont d’autres responsabilités, comme la coordination de tâches, ou de la modélisation de l’état général d’un système. Lorsqu’un objet assume une responsabilité dont dépendent d’autres objets. Il est nécessaire d’avoir une méthode pour localiser cet objet. Souvent, lorqu’on cherche un responsable, ce dernier est unique. Une solution courante est l’utilisation d’un Singleton.

L’objectif du pattern Singleton est de garantir qu’une classe ne possède qu’une seule instance et de fournir un point d’accès global à celle-ci.

Implémentation en PHP

 
<?php
class Singleton { 
    private static $_instance; 
    
    private function __construct () {

    } 
    
    private function __clone () {

    } 
    public static function getInstance () { 
        if (!(self::$_instance instanceof self)) 
            self::$_instance = new self(); 
        return self::$_instance; 
    } 
    public function action() {} 
} 
<?php
Singleton::getInstance()->action(); 

Problème posé par PHP antérieur à 5.3

Jusqu’à la version 5.3, il n’était pas possible d’utiliser l’héritage pour les Singleton. l’accesseur self:: se référant toujours à la classe dans lequel il est écrit. Il est donc nécessaire de dupliquer systématiquement la définition de la méthode static lang=“php”>getInstance() dans tous les enfants de notre Singleton.

<?php
class MySingleton extends Singleton { 
    ... 
    public static function getInstance () { 
        if (!(self::$_instance instanceof self)) 
            self::$_instance = new self(); 
            return self::$_instance; 
        } 
        ... 
    }

L’apparition du Late Static Bindings dans PHP 5.3 résout ce problème. L’accesseur static:: est similaire à self:: mais il se réfère à la classe dans laquelle il est appelé. Il peut donc être hérité.

Critiques

Le Singleton est souvent considéré à juste raison comme un anti-pattern, il introduit un couplage fort entre tous les composants qui l’utilisent. Pour qu’une classe soit testable, il faut qu’elle soit le plus indépendant possible des autres classes, ce qui n’est pas le cas avec un Singleton.

Le couplage fort peut être parfaitement justifié, par exemple le Front Controller dans Zend Framework est un singleton. Il n’est pas irrationnel d’introduire une dépendance forte avec le chef d’orchestre du modèle MVC.

Injections de dépendances

Par contre, il est mal utilisé lorsqu’il vient en remplacement d’une variable globale. On connait tous les raisons qui font que l’on ne doit pas utiliser de variable globale, ces mêmes règles s’appliquent dans ce cas au singleton. Prenons l’exemple de la connexion à une base de données, on pourrait mettre en place un singleton pour unifier l’accès à la base de donnée.

<?php 
class DbMysql { 
    private static $_instance; 
    private function __construct() {} 
    private function __clone() {} 
    public static function getInstance() { 
        if (!(self::$_instance instanceof self)) { 
            self::$_instance = new self(); 
        } 
        return self::$_instance; 
    } 
    
    public function connect() { 
        // do connection stuff 
    }     
} 

Typiquement on utilisera une connexion à une base de données pour l’authentification.

<?php 
class User { 
    public function __construct() { 
        // dépendance avec la classe Db 
        $db = DbMysql::getInstance(); 
    } 
} 

Si l’on veut changer de base de données, et passer à Postgres par exemple, il faudra créer une classe DbPostgres et modifier tous les appels à DbMysql::getInstance() par DbPostgres::getInstance(). Ce qui n’est pas acceptable, on a clairement ici un problème de conception.

Une des solutions possible est d’inverser le flux de contrôle de notre application: au lieu que ce soit la classe User qui appelle directement DbMysql. On peut créer l’instance de la classe en amont, et ensuite l’injecté dans la classe qui souhaite l’utiliser.

Ce principe à un nom, il s’agit de l’injection de dépendances. On obtiendra ainsi un code du type :

<?php
interface Db { 
    public function connect(); 
} 

class DbMysql implements Db { 
    /* ... */ 
} 

class Postgresql implements Db { 
    /* ... */ 
} 

class User { 
    public function __construct(Db $db) { 
        /* ... */ 
    } 
}

La dépendance a changé, elle ne vient plus du code mais de l’interface. C’est beaucoup mieux. De plus, les tests sont plus facile à réaliser, on peut facilement faire un objet Mock implémentant l’interface Db. Ce qui n’était pas possible avec le Singleton de départ. C’est juste de l’injection de dépendances. On aurait pu aussi passer par des setters pour injecter la dépendance de notre classe User.

comments powered by Disqus

Voir aussi

Zend Framework 1.9 : compatible avec PHP 5.3 et PHP 5.2

Zend Framework 1.9 : compatible avec PHP 5.3 et PHP 5.2

La “PHP Company” Zend sort la cinquième version de son célèbre framework en moins de 2 ans d’existence. Cette version apporte son …

Utilité des langages de templates en PHP

Utilité des langages de templates en PHP

Les langages de templates sont apparus en PHP il y a près de 10 ans. Leur utilisation s’est répandu, mais sont-ils vraiment utiles ? PHP …

Supprimer un répertoire récursivement en PHP

Supprimer un répertoire récursivement en PHP

La fonction rmdir de PHP tente d’effacer un dossier dont le chemin est passé en paramètre. Le dossier doit être vide pour pouvoir être effacé. …

A la une
  • Rencontre du numérique 2019 - Nîmes
  • référencement naturel d'un hôtel
  • Développeur eZ Platform
  • Tech lead Symfony
  • Expert Qualité Web

Copyright - Sylvain FIX

2009 - 2019