De l’usage des méthodes statiques

Suite à ma précédente introduction à Domain Driven Design, une critique négative sur l’usage des services statiques (en PHP ou non)a fait réagir un lecteur. Ce dernier soulève une question légitime : « en quoi l’usage des classes de services statiques est à éviter ? »

Un service statique ?

Tout d’abord, rappelons d’un point de vue technique ce que sont les méthodes statiques :

<?php
class ClassDeServiceStatique
{
    public static function unServiceStatique () 
    { 
        //L'usage de $this est prohibé
        //calculs ne nécessitant pas de contexte
        return "resultat";
    }
}

Une classe de service statique est une classe qui regroupe ses règles métiers dans une collection de méthodes exclusivement statiques (utilisant le mot clef static), donc qui n’utilise ni ne s’appuie sur une quelconque information de contexte (un état).

Que reprocher à cette façon de procéder ?

Programmer de la sorte rappelle sans conteste une approche procédurale de la programmation. La programmation procédurale n’est pas un mal, mais pourquoi alors vouloir déguiser ses procédures en objets ?

Le temps des interfaces

Afin de faciliter l’interchangeabilité des implémentations (sans obligatoirement passer par l’héritage), les langages de programmation ont introduit la notion d’interface, de contrat. Par exemple, il peut exister un contrat « EcrireSurDuPapier », ce contrat peut être remplit par l’objet Imprimante ou par l’objet Secretaire.

Un service ayant besoin d’obtenir une confirmation écrite dépend donc de ce contrat d’écriture, non d’un(e) Secrétaire ou d’une Imprimante.

En PHP, on peut encourager l’usage de ce concept.

<?php
interface ServiceEcriturePapier
{
    public function ecrireSur ($texte, Papier $papier);
}

class Ecrivain implements ServiceEcriturePapier
{
    public function ecrireSur ($texte, Papier $papier)
    {
        $papier->appliquerEncre($this->stylo->getEncre(), $texte);
    }
}

class Imprimante implements ServiceEcriturePapier
{
    public function ecrireSur ($texte, Papier $papier)
    {
        $papier->appliquerEncre($this->cartouche->getEncre(), $texte);
    }
}

class ServiceReservation
{
    public function __construct (ServiceEcriturePapier $serviceEcriture)
    {
        //La dépendance est faite sur un service d'écriture, non sur une implémentation particulière
        $this->serviceEcriture = $serviceEcriture;
    }

    public function reserve (Personne $personne, Spectacle $spectacle, Horaires $horaires)
    {
        //procède à la réservation...
        //...
        $lettre = $this->serviceEcriture->ecrire($this->getMessageConfirmation(),
                                                 $this->getPapierALettre()
                                                 );
        //...
    }
}

Si l’on veut bénéficier de ce mécanisme d’interface pour nos services, et que certains de nos services sont statiques, il faudra déclarer dans le contrat lui-même que l’implémentation est statique.

En effet, en PHP, le caractère statique ou non d’une méthode fait partie du contrat, de l’interface. Si demain nous arrivons à une meilleure implémentation de l’algorithme, nous serons malheureusement tenus de l’implémenter de façon statique (et donc sans contexte) afin de respecter un vieux choix d’implémentation.

//Valide mais non recommandable
interface ServiceEcriture
{
    public static function ecrire ();
}

class ServiceEcritureEncre implements ServiceEcriture
{
    //La méthode DOIT être statique pour être syntaxiquement valide
    public static function ecrire ()
    {
        //implémentation
    }
}

Autre interrogation que l’on peut avoir : si une interface représente le contrat rempli par un objet, quel sens d’indiquer dans ce contrat une méthode statique n’ayant pas besoin de contexte, et par extension n’ayant pas besoin d’objet ?

Tout ceci est tiré par les cheveux…

Je l’admet volontier, tout ceci est tiré par les cheveux : Ne pas utiliser de services statiques pour ne pas forcer de futures implémentations potentielles à respecter des interfaces hypothétiques, voilà un risque que plusieurs d’entre nous voudrons bien prendre. Toutefois, cela permet d’entrevoir les failles d’une telle pratique.

Durcissons les liens

Si nos méthodes sont statiques, c’est qu’elles sont utilisées de façon statiques également (sinon quel intérêt ?)

class Console
{
    public static function write ($text)
    {
        echo $text;
    }
}

class ProgrammeDePoesie
{
    public function redigePoesieBinaire ()
    {
        $poeme = $this->redigePoesie();
        Console::write($poeme);
    }
}

Ici l’inconvénient est net : notre programme de génération de poèmes ne fonctionne QUE avec la sortie console alors qu’en réalité il n’aurait besoin que d’un contrat d’écriture, quel qu’il soit.

L’une des forces de la programmation objet est entre autres d’encourager les mécanismes de polymorphisme et de composition. Utiliser des classes statiques, c’est s’interdire de décorer, adapter, proposer un proxy, utiliser une fabrique pour récupérer la meilleure stratégie. Utiliser des méthodes statiques c’est s’interdire de garder l’instance du service dans une collection ou dans une propriété, c’est se priver de composition.

Une evolution « dans le mur »

Rien ne garantit que nos services, aussi simples soient-t-ils, n’évolueront pas et n’auront pas un jour besoin de contexte.

Dans notre exemple, nous pourrions avoir besoin de sortir le texte dans une couleur particulière. L’évolution la plus simple serait alors l’horrible chose suivante :

class ProgrammeDePoesie
{
    public function redigePoesieBinaire ()
    {
        $poeme = $this->redigePoesie();
        $oldColor = Console::getColor();
        Console::setColor('rouge');
        Console::write($poeme);
        Console::setColor($oldColor);
    }
}

//Noir par défaut
Console::setColor('noir');

$barde = new ProgrammeDePoesie();
$barde->redigePoesie();

La boucle est bouclée, nous manipulons des procédures qui usent de variables globales (une instance unique déguisée), ou chaque code client manipule l’état interne de l’objet qui n’en est pas un…

Trop de détails tue le détail

Au final, indiquer qu’une méthode est statique, c’est dévoiler un détail d’implémentation inutile au client, c’est violer le principe de l’encapsulation, de l’abstraction.

En programmation objet, utiliser et s’appuyer sur des objets est bien plus prudent.

  1. En d’autres termes, les méthodes statiques, c’est du procédural de luxe :mrgreen:

    J’aurais peut-être ajouté un élément de réflexion supplémentaire : les classes et/ou méthodes statiques, ça pouvait se justifier bien davantage avec le début de modèle objet de PHP 4. En PHP 5, ça se justifie largement moins, et ce d’autant moins avec PHP 5.4 que si dans une (ou plusieurs) classe(s) on a besoin ponctuellement d’une simple méthode sans devoir initier un objet ni devoir (ou pouvoir) utiliser un héritage quelconque, on a maintenant les traits. :)

    My 2¢

  2. Choisir les statiques c’est aussi choisir de suivre l’architecture actuelle de PHP. Imaginons dans un future proche qu’un très bon serveur d’application PHP fasse son apparition et que mes statiques deviennent réellement statiques ala Java (= persistante entre 2 executions du programme). Cela signifie que mon programme sera exposé à de la fuite de contexte si j’ai utilisé les statiques sans les maitriser. Personnellement, je n’utilise static en PHP que pour la mise en place de Singleton et encore plus je peux m’en passer mieux je me porte.

  3. Je suis plutôt d’accord, mais j’utilise quand même du static par exemple pour du traitement de chaine.

    Ou est-ce que vous mettriez une petite fonction de base qui génère un mot de passe sur x caractères ?

    Ou une fonction d’urification ?

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>