01/08/2011

Redonner son sens à l'héritage grâce aux Traits

Suite à la rédaction d'un petit article pour PHP Solutions sur l'approche Orientée Objet, je me suis mis à faire quelques tests pour voir jusqu'à quel point les Traits de PHP 5.4 pourront changer notre manière de programmer.

Et bien ça fait plus que changer, les Traits sont à mon avis une véritable révolution dans l'approche et la conceptualisation d'un code, quel qu'il soit.

En effet, un Trait permet de réutiliser un Comportement. Or sur un gros projet, même en organisant bien notre code, on est souvent amené à recoder plusieurs fois le même comportement. Par exemple, pour créer un Singleton on a deux choix :

  1. ou bien on écrit tout le code nécessaire dans notre classe
  2. ou bien on fait hériter notre classe d'une classe mère Singleton

Dans le premier cas, on a inévitablement du code redondant dans notre application.

Dans le second, on perd le principe de l'héritage : l'héritage ne sert pas ici à spécialiser un comportement métier, mais à disposer d'outils pour un fonctionnement interne. On perd alors totalement ce principe de l'Orienté Objet qui consiste réduire le couplage entre les modules métiers et les modules internes ; L'héritage en PHP n'étant pas multiple, on perd le seul héritage métier/fonctionnel que l'on aurait pu avoir pour notre classe.

Un solution consisterait à augmenter le niveau d'héritage et de créer une hiérarchie de classes (c'est ce qui est souvent fait, vu que l'héritage multiple n'existe pas en PHP). Mais dans ce cas :

  • on a perdu la logique objet : l'héritage n'a pas servi à spécialiser
  • le code n'est plus maintenable
  • l'architecture n'est plus maintenable (on est pris dans un cercle vicieux, celui de rajouter toujours un niveau au dessus jusqu'à créer une classe géante à la racine qui fait tout)

Bref, à mon avis sur ce genre de code on est souvent dans une impasse. C'est là qu'interviennent les Traits. Plutôt que de consacrer l'héritage à un comportement interne, laissons l'héritage pour le comportement métier et utilisons les traits pour ce qui est interne.

Attention, je ne dis pas que les Traits ne peuvent pas être utilisés pour du comportement métier (bien au contraire), ne me faites pas dire ce que je n'ai pas dit :-)

En voici un exemple simple. Cet exemple n'est pas idéal, dans la mesure où il s'agit plus d'un détournement des Traits que d'une utilisation plus intelligente, mais je pense qu'il est clair :

trait Singleton {
    /**
     * Constructor
     *
     */
    protected function __construct() {}
     /**
     * Get singleton instance
      *
     * @return static
     */
    public static function getInstance() {

        static $instance = null;
        if (is_null($instance)) {
            $instance = new static;
        }

        return $instance;
    }

    /**
     * Prevents cloning
     *
     * @throws Exception
     */
    public function __clone() {
        throw new \Exception('Cloning of this object isn\'t authorized');
    }

    /**
     * Prevents deserialization
     *
     * @throws Exception
     */
    public function __wakeup() {
        throw new \Exception("Cannot deserialize instance of Singleton pattern in" . get_called_class());
    }
}

Il ne reste plus qu'à l'utiliser :

class Example extends MaClasseMetier {
    use \Singleton;
}
$oExample = Example::getInstance();
var_dump($oExample === Example::getInstance());
// true
$oExample = new Example;
// Fatal error: Call to protected Example::__construct() from invalid context

Ce détournement nous permet d'utiliser le Comportement "Singleton" pour toutes les classes qui en ont besoin, avec un simple "use Singleton". Bon, il ne faut pas forcément le faire, et je sens les remarques qui vont arriver sur le Singleton et la testabilité, mais je pense que c'est un exemple clair des horizons que nous ouvrent les Traits :-p

blog comments powered by Disqus