web-dev-qa-db-fra.com

Si les singletons sont mauvais, pourquoi un conteneur de service est-il bon?

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

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

Pour résoudre ce problème, on m'a dit d'utiliser un soi-disant "gestionnaire d'objets" (ou conteneur de services comme symfony) qui stocke en interne chaque référence aux services (enregistreur, etc.).

Mais pourquoi un fournisseur de services n'est-il pas aussi mauvais qu'un simple singleton?

Le fournisseur de services masque également les dépendances et conclut simplement la création de la première istance. J'ai donc vraiment du mal à comprendre pourquoi nous devrions utiliser un fournisseur de services au lieu de singletons.

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

Ajouter

J'ajouterais: Ces jours-ci, les singletons ne sont pas si mal, 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 assez intelligent même si cela ne résout pas du tout tous les problèmes.

Autre que DI et Service Container existe-t-il une bonne solution acceptable pour accéder à ces objets d'assistance?

88
dynamic

Service Locator n'est que le moindre de deux maux pour ainsi dire. Le "moindre" se résume à ces quatre différences ( au moins, je ne peux pas penser à d'autres en ce moment ):

Principe de responsabilité unique

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

Couplage

Les singletons sont généralement codés en dur dans votre application en raison des appels de méthode statique, ce qui conduit à dépendances étroites et difficiles à simuler dans votre code. Le SL d'autre part est juste une classe et il peut être injecté. Ainsi, alors que tout votre classe en dépendra, au moins il s'agit d'une dépendance faiblement couplée. Donc, à moins que vous n'ayez implémenté ServiceLocator en tant que singleton lui-même, c'est un peu mieux et aussi plus facile à tester.

Cependant, toutes les classes utilisant le ServiceLocator dépendent désormais du ServiceLocator, qui est également une forme de couplage. Cela peut être atténué en utilisant une interface pour ServiceLocator afin que vous ne soyez pas lié à une implémentation concrète de ServiceLocator, mais vos classes dépendront de l'existence d'une sorte de localisateur, alors que le fait de ne pas utiliser de ServiceLocator augmente la réutilisation de façon spectaculaire.

Dépendances cachées

Le problème de la dissimulation des dépendances existe cependant beaucoup. Lorsque vous injectez simplement le localisateur à vos classes consommatrices, vous ne connaîtrez aucune dépendance. Mais contrairement au Singleton, le SL instanciera 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 comme Misko Hevery dans l'exemple CreditCard , par exemple vous n'avez pas à instancier toutes les dépendances des dépendances à la main.

Récupérer les dépendances à l'intérieur de l'instance viole également Law of Demeter , qui stipule que vous ne devez pas creuser dans les collaborateurs. Une instance ne doit parler qu'à ses collaborateurs immédiats. Il s'agit d'un problème 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 localisateur de service entre les tests, toutes les instances précédemment créées sont également supprimées (sauf si vous avez fait l'erreur et les avez enregistrées dans des attributs statiques dans la SL). Bien sûr, cela ne vaut pour aucun état global dans les classes gérées par le SL.

Voir également Fowler sur Service Locator vs Dependency Injection pour une discussion beaucoup plus approfondie.


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

75
Gordon

Le modèle de localisateur de service est un anti-modèle. Cela ne résout pas le problème de l'exposition des dépendances (vous ne pouvez pas dire en regardant la définition d'une classe quelles sont ses dépendances car elles ne sont pas injectées, elles sont plutôt retirées du localisateur de service).

Donc, votre question est: pourquoi les localisateurs de services sont-ils bons? Ma réponse est: ils ne le sont pas.

Évitez, évitez, évitez.

42
jason

Le conteneur de services masque les dépendances comme le fait le modèle Singleton. Vous voudrez peut-être suggérer d'utiliser des conteneurs d'injection de dépendance à la place, car il présente tous les avantages du conteneur de service mais pas (à ma connaissance) d'inconvénients du conteneur de service.

Si je comprends bien, la seule différence entre les deux est que dans le conteneur de service, le conteneur de service est l'objet en cours d'injection (masquant ainsi les dépendances), lorsque vous utilisez DIC, le DIC injecte les dépendances appropriées pour vous. La classe gérée par le DIC est complètement inconsciente du fait qu'elle est gérée par un DIC, vous avez donc moins de couplage, des dépendances claires et des tests unitaires satisfaisants.

C'est une bonne question à SO expliquant la différence des deux: Quelle est la différence entre les modèles d'Injection de dépendances et Localisateur de services?

4
rickchristie

Parce que vous pouvez facilement remplacer des objets dans Service Container par
1) héritage (la classe Object Manager peut être héritée et les méthodes peuvent être remplacées)
2) modification de la configuration (dans le cas de Symfony)

Et, les singletons sont mauvais non seulement à cause du couplage élevé, mais parce qu'ils sont _Single _ tonnes. C'est une mauvaise architecture pour presque toutes sortes d'objets.

Avec DI pur (dans les constructeurs), vous paierez un prix très élevé - 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 performances. De plus, il n'est pas toujours possible de créer un objet et de le passer dans un constructeur - une chaîne de dépendances peut être créée ... Mon anglais n'est pas assez bon pour en discuter complètement, lisez-le dans la documentation Symfony.

2
OZ_

Pour moi, j'essaie d'éviter les constantes globales, les singletons pour une raison simple, il y a des cas où j'ai besoin d'exécuter des API.

Par exemple, j'ai front-end et admin. À l'intérieur de l'administrateur, je veux qu'ils puissent se connecter en tant qu'utilisateur. Considérez le code dans admin.

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

Cela peut établir une nouvelle connexion à la base de données, un nouvel enregistreur, etc. pour l'initialisation frontale et vérifier si l'utilisateur existe réellement, valide, etc. Il utilisera également des cookies et des services de localisation distincts appropriés.

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

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

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

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

0
romaninsh