web-dev-qa-db-fra.com

Avantages et inconvénients des constantes d'interface

Les interfaces PHP permettent la définition de constantes dans une interface, par ex.

interface FooBar
{
    const FOO = 1;
    const BAR = 2;
}
echo FooBar::FOO; // 1

Toute classe d'implémentation aura automatiquement ces constantes disponibles, par exemple.

class MyFooBar implement FooBar
{
}
echo MyFooBar::FOO; // 1

Ma propre opinion à ce sujet est que tout ce qui est mondial est mauvais . Mais je me demande si la même chose s'applique aux constantes d'interface. Étant donné que Codage par rapport à une interface est considéré comme une bonne pratique en général, l'utilisation des constantes d'interface est-elle les seules constantes acceptables à utiliser en dehors d'un contexte de classe?

Bien que je sois curieux d'entendre votre opinion personnelle et si vous utilisez ou non des constantes d'interface, je recherche principalement des raisons objectives dans vos réponses. Je ne veux pas que ce soit une question de type de sondage. Je suis intéressé par l'effet que l'utilisation des constantes d'interface a sur la maintenabilité. Couplage. Ou tests unitaires. Comment est-ce lié à SOLIDE PHP? Cela viole-t-il les principes de codage considérés comme de bonnes pratiques en PHP? Vous avez eu l'idée …

Remarque: il y a question similaire pour Java qui énumère quelques bonnes raisons pour lesquelles elles sont de mauvaises pratiques, mais comme Java n'est pas PHP, j'ai senti qu'il était justifié de le demander dans la balise PHP encore.

101
Gordon

Eh bien, je pense que cela se résume à la différence entre bon et assez bon .

Alors que dans la plupart des cas, vous pouvez éviter l'utilisation de constantes en implémentant d'autres modèles (stratégie ou peut-être poids mouche), il y a quelque chose à dire pour ne pas avoir besoin d'une demi-douzaine d'autres classes pour représenter un concept. Je pense que cela se résume à la probabilité de besoin d'autres constantes. En d'autres termes, est-il nécessaire d'étendre l'ENUM fourni par les constantes sur l'interface. Si vous pouvez prévoir la nécessité de l'étendre, optez pour un modèle plus formel. Sinon, cela peut suffire (ce sera assez bon, et donc moins de code à écrire et à tester). Voici un exemple d'une utilisation assez bonne et mauvaise:

Mauvais:

interface User {
    const TYPE_ADMINISTRATOR = 1;
    const TYPE_USER          = 2;
    const TYPE_GUEST         = 3;
}

Assez bien:

interface HTTPRequest_1_1 {
    const TYPE_CONNECT = 'connect';
    const TYPE_DELETE  = 'delete';
    const TYPE_GET     = 'get';
    const TYPE_HEAD    = 'head';
    const TYPE_OPTIONS = 'options';
    const TYPE_POST    = 'post';
    const TYPE_PUT     = 'put';

    public function getType();
}

Maintenant, la raison pour laquelle j'ai choisi ces exemples est simple. L'interface User définit une énumération de types d'utilisateurs. Il est très probable que cela se développe avec le temps et serait mieux adapté par un autre schéma. Mais le HTTPRequest_1_1 est un cas d'utilisation décent, car l'énumération est définie par RFC2616 et ne changera pas pendant la durée de vie de la classe.

En général, je ne vois pas le problème avec les constantes et les constantes de classe comme étant un problème global . Je le vois comme un problème de dépendance. C'est une distinction étroite, mais définitive. Je vois des problèmes globaux comme dans les variables globales qui ne sont pas appliquées, et en tant que tels créent une dépendance globale douce. Mais une classe codée en dur crée une dépendance forcée, et en tant que telle, crée une dépendance globale dure. Les deux sont donc des dépendances. Mais je considère que le global est bien pire car il n'est pas appliqué ... C'est pourquoi je n'aime pas le lump dépendances de classe avec dépendances globales sous la même bannière ...

Si vous écrivez MyClass::FOO, vous êtes codé en dur pour les détails d'implémentation de MyClass. Cela crée un couplage dur, ce qui rend votre code moins flexible et doit donc être évité. Cependant, des interfaces existent pour permettre exactement ce type de couplage. Donc MyInterface::FOO n'introduit aucun couplage en béton. Cela dit, je n'introduirais pas une interface juste pour y ajouter une constante.

Donc, si vous utilisez des interfaces et que vous êtes très sûr que vous (ou toute autre personne d'ailleurs) n'aurez pas besoin de valeurs supplémentaires, alors je ne vois pas vraiment d'énorme problème avec le constantes d'interface ... Les meilleures conceptions n'incluraient pas de constantes ou de conditions ou de nombres magiques ou de chaînes magiques ou quoi que ce soit codé en dur. Cependant, cela ajoute du temps supplémentaire au développement, car vous devez considérer les utilisations. À mon avis, la plupart du temps, cela vaut vraiment la peine de prendre le temps supplémentaire pour construire un superbe design solide. Mais il y a des moments où assez bon est vraiment acceptable (et il faut un développeur expérimenté pour comprendre la différence), et dans ces cas, ça va.

Encore une fois, c'est juste mon point de vue là-dessus ...

132
ircmaxell

Je pense qu'il est généralement préférable de gérer les constantes, spécialement les constantes énumérées, en tant que type distinct ("classe") de votre interface:

define(TYPE_CONNECT, 'connect');
define(TYPE_DELETE , 'delete');
define(TYPE_GET    , 'get');
define(TYPE_HEAD   , 'head');
define(TYPE_OPTIONS, 'options');
define(TYPE_POST   , 'post');
define(TYPE_PUT    , 'put');

interface IFoo
{
  function /* int */ readSomething();
  function /* void */ ExecuteSomething(/* int */ param);
}

class CBar implements IFoo
{
  function /* int */ readSomething() { ...}
  function /* void */ ExecuteSomething(/* int */ param) { ... }
}

ou, si vous souhaitez utiliser une classe comme espace de noms:

class TypeHTTP_Enums
{
  const TYPE_CONNECT = 'connect';
  const TYPE_DELETE  = 'delete';
  const TYPE_GET     = 'get';
  const TYPE_HEAD    = 'head';
  const TYPE_OPTIONS = 'options';
  const TYPE_POST    = 'post';
  const TYPE_PUT     = 'put';
}

interface IFoo
{
  function /* int */ readSomething();
  function /* void */ ExecuteSomething(/* int */ param);
}

class CBar implements IFoo
{
  function /* int */ readSomething() { ...}
  function /* void */ ExecuteSomething(/* int */ param) { ... }
}

Ce n'est pas que vous utilisez uniquement des constantes, vous utilisez le concept de valeurs énumérées ou énumérations, qui un ensemble de valeurs restreintes, sont considérées comme un type spécifique, avec un usage spécifique ("domaine"?)

10
umlcat