90 votes

Si les singletons sont mauvais, pourquoi les conteneurs de services sont-ils bons ?

Nous savons tous comment mauvais Singletons sont parce qu'ils cachent les dépendances et pour autres raisons .

Mais dans un cadre, il peut y avoir de nombreux objets qui ne doivent être instanciés qu'une seule fois et appelés de partout (logger, db etc).

Pour résoudre ce problème, on m'a dit d'utiliser un "gestionnaire d'objets" (ou "Objects Manager"). Conteneur de service comme symfony) qui stocke en interne chaque référence aux services (logger etc).

Mais pourquoi un Service Provider n'est-il pas aussi mauvais qu'un Singleton pur ?

Le fournisseur de services cache aussi les dépendances et il se contente d'envelopper la création de la première istance. J'ai donc vraiment du mal à comprendre pourquoi nous devrions utiliser un fournisseur de services plutôt que des singletons.

PS. Je sais que pour ne pas cacher les dépendances, je dois utiliser DI (comme indiqué par Misko).

Ajouter

Je voudrais ajouter : De nos jours, les singletons ne sont pas si mauvais, le créateur de PHPUnit l'a expliqué ici :

DI + Singleton résout le problème :

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

c'est plutôt intelligent même si cela ne résout pas tous les problèmes.

Autres que DI et Service Container y a-t-il de bonnes acceptations solution pour accéder à ces objets d'aide ?

75voto

Gordon Points 156415

Service Locator n'est que le moindre des deux maux, pour ainsi dire. Le "moindre" se résume à ces quatre différences ( en tout cas, je n'en vois pas d'autres pour le moment. ):

Principe de responsabilité unique

Le conteneur de service ne viole pas le principe de responsabilité unique comme le fait le singleton. Les singletons mélangent la création d'objets et la logique métier, tandis que le Service Container est strictement responsable de la gestion des cycles de vie des objets de votre application. À cet égard, le Service Container est meilleur.

Accouplement

Les singletons sont généralement codés en dur dans votre application en raison des appels de méthodes statiques, ce qui conduit à couplage étroit et dépendances difficiles à simuler dans votre code. Le SL, quant à lui, n'est qu'une classe et il peut être injecté. Ainsi, même si toutes vos classes en dépendent, il s'agit au moins d'une dépendance faiblement couplée. Donc, à moins que vous n'implémentiez le ServiceLocator comme un Singleton lui-même, c'est un peu mieux et aussi plus facile à tester.

Cependant, toutes les classes utilisant le ServiceLocator dépendront désormais de ce dernier, ce qui constitue également une forme de couplage. Cela peut être atténué en utilisant une interface pour le ServiceLocator afin de ne pas être lié à une implémentation concrète du ServiceLocator mais vos classes dépendront de l'existence d'une sorte de Locator alors que ne pas utiliser de ServiceLocator du tout augmente considérablement la réutilisation.

Dépendances cachées

Le problème de la dissimulation des dépendances existe cependant bel et bien. Lorsque vous injectez simplement le localisateur à vos classes consommatrices, vous ne connaissez pas les dépendances. Mais contrairement au Singleton, le SL instancie généralement toutes les dépendances nécessaires dans les coulisses. Ainsi, lorsque vous récupérez un service, vous ne vous retrouvez pas dans la situation suivante Misko Hevery dans l'exemple de CreditCard Par exemple, vous n'avez pas à instancier toutes les dépendances à la main.

Récupérer les dépendances à partir de l'intérieur de l'instance est également une violation Loi de Déméter qui stipule que vous ne devez pas fouiller dans les collaborateurs. Une instance ne doit parler qu'à ses collaborateurs immédiats. C'est un problème à la fois avec Singleton et ServiceLocator.

État global

Le problème de l'état global est également quelque peu atténué car lorsque vous instanciez un nouveau Service Locator entre les tests, toutes les instances précédemment créées sont également supprimées (à moins que vous n'ayez commis l'erreur de les sauvegarder dans des attributs statiques dans la SL). Cela n'est pas vrai pour tout état global dans les classes gérées par le SL, bien sûr.

Voir également Fowler sur Localisation de services et injection de dépendances pour une discussion beaucoup plus approfondie.


Une note sur votre mise à jour et l'article lié par Sebastian Bergmann sur le test du code qui utilise des singletons : Sebastian ne suggère en aucun cas que la solution de contournement proposée rend l'utilisation des Singleons moins problématique. C'est juste une façon de rendre plus testable un code qui serait autrement impossible à tester. Mais c'est toujours un code problématique. En fait, il note explicitement : "Juste parce que vous pouvez, ne signifie pas que vous devriez".

42voto

Jason Points 125291

Le modèle du localisateur de services est un anti-modèle. Il ne résout pas le problème de l'exposition des dépendances (vous ne pouvez pas savoir, en regardant la définition d'une classe, quelles sont ses dépendances parce qu'elles ne sont pas injectées, mais extraites du localisateur de services).

Votre question est donc la suivante : pourquoi les localisateurs de services sont-ils bons ? Ma réponse est la suivante : ils ne le sont pas.

Éviter, éviter, éviter.

4voto

rickchristie Points 672

Le conteneur de service cache les dépendances comme le fait le modèle Singleton. Vous pourriez suggérer d'utiliser des conteneurs d'injection de dépendances à la place, car ils ont tous les avantages des conteneurs de services, mais pas (pour autant que je sache) les inconvénients des conteneurs de services.

D'après ce que je comprends, la seule différence entre les deux est que dans le conteneur de service, le conteneur de service est l'objet injecté (cachant ainsi les dépendances), alors que lorsque vous utilisez le DIC, le DIC injecte les dépendances appropriées pour vous. La classe gérée par le DIC n'est absolument pas consciente du fait qu'elle est gérée par un DIC, ce qui réduit le couplage, clarifie les dépendances et rend les tests unitaires heureux.

C'est une bonne question à SO qui explique la différence entre les deux : Quelle est la différence entre les modèles d'injection de dépendances et de localisation de services ?

2voto

OZ_ Points 7398

Parce que vous pouvez facilement remplacer les objets dans le conteneur de service par
1) l'héritage (la classe du gestionnaire d'objets peut être héritée et les méthodes peuvent être remplacées)
2) changer la configuration (dans le cas de Symfony)

Et les singletons sont mauvais non seulement à cause du couplage élevé, mais aussi parce qu'ils sont ________________. Simple _tons. C'est une mauvaise architecture pour presque tous les types d'objets.

Avec un DI 'pur' (dans les constructeurs), vous allez payer très cher - tous les objets doivent être créés avant d'être passés dans le constructeur. Cela signifie plus de mémoire utilisée et moins de performance. De plus, un objet ne peut pas toujours être créé et passé dans le constructeur - une chaîne de dépendances peut être créée... Mon anglais n'est pas assez bon pour en parler complètement, lisez la documentation de Symfony à ce sujet.

0voto

romaninsh Points 6048

En ce qui me concerne, j'essaie d'éviter les constantes globales, les singletons pour une raison simple, il y a des cas où je peux avoir besoin de faire fonctionner des API.

Par exemple, j'ai un front-end et un admin. Dans l'admin, je veux qu'ils soient en mesure de se connecter en tant qu'utilisateur. Considérez le code dans l'admin.

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

Cela peut établir une nouvelle connexion à la base de données, un nouveau logger, etc. pour l'initialisation du front-end et vérifier si l'utilisateur existe réellement, s'il est valide, etc. Il utilise également des cookies et des services de localisation distincts.

Mon idée de singleton est - Vous ne pouvez pas ajouter le même objet dans le parent deux fois. Par exemple

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

vous laisserait avec une seule instance et les deux variables pointant vers elle.

Enfin, si vous souhaitez utiliser le développement orienté objet, travaillez avec des objets, et non avec des classes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X