web-dev-qa-db-fra.com

Injection de dépendance et modèle de conception singleton

Comment identifier quand utiliser l'injection de dépendance ou le modèle singleton. J'ai lu dans beaucoup de sites Web où ils disent "Utiliser l'injection de dépendance sur le modèle singleton". Mais je ne sais pas si je suis totalement d'accord avec eux. Pour mes projets à petite ou moyenne échelle, je vois clairement l'utilisation du motif singleton simple.

Par exemple Logger. Je pourrais utiliser Logger.GetInstance().Log(...) Mais, au lieu de cela, pourquoi dois-je injecter chaque classe que je crée, avec l'instance de l'enregistreur?.

73
SysAdmin

Si vous voulez vérifier ce qui est enregistré dans un test, vous avez besoin d'une injection de dépendance. De plus, un enregistreur est rarement un singleton - généralement vous avez un enregistreur pour chacune de vos classes.

Regardez cette présentation sur la conception orientée objet pour la testabilité et vous verrez pourquoi les singletons sont mauvais.

Le problème avec les singletons est qu'ils représentent un état global difficile à prévoir, en particulier dans les tests.

Gardez à l'esprit qu'un objet peut être de facto singleton mais toujours être obtenu via l'injection de dépendance, plutôt que via Singleton.getInstance().

Je ne fais qu'énumérer quelques points importants soulevés par Misko Hevery dans sa présentation. Après l'avoir regardé, vous comprendrez pourquoi il vaut mieux qu'un objet définisse quelles sont ses dépendances, mais ne définit pas de chemin comment les créer .

54
Bozho

Les singletons sont comme le communisme: ils sonnent tous les deux très bien sur le papier, mais explosent de problèmes dans la pratique.

Le motif singleton met un accent disproportionné sur la facilité d'accès aux objets. Il évite complètement le contexte en exigeant que chaque consommateur utilise un objet de portée AppDomain, ne laissant aucune option pour différentes implémentations. Il intègre des connaissances d'infrastructure dans vos classes (l'appel à GetInstance()) tout en ajoutant exactement zéro puissance expressive. Cela diminue en fait votre pouvoir expressif, car vous ne pouvez pas changer l'implémentation utilisée par une classe sans la changer pour tous d'entre eux. Vous ne pouvez tout simplement pas ajouter des fonctionnalités uniques.

De plus, lorsque la classe Foo dépend de Logger.GetInstance(), Foo cache effectivement ses dépendances aux consommateurs. Cela signifie que vous ne pouvez pas pleinement comprendre Foo ou l'utiliser en toute confiance à moins de lire sa source et de découvrir le fait que cela dépend de Logger. Si vous n'avez pas la source, cela limite votre capacité à comprendre et à utiliser efficacement le code dont vous dépendez.

Le modèle singleton, tel qu'il est implémenté avec des propriétés/méthodes statiques, n'est guère plus qu'un hack autour de l'implémentation d'une infrastructure. Il vous limite de multiples façons tout en n'offrant aucun avantage perceptible par rapport aux alternatives. Vous pouvez l'utiliser comme vous le souhaitez, mais comme il existe des alternatives viables qui favorisent une meilleure conception, cela ne devrait jamais être une pratique recommandée.

83
Bryan Watts

D'autres ont très bien expliqué le problème des singletons en général. Je voudrais juste ajouter une note sur le cas spécifique de l'enregistreur. Je suis d'accord avec vous que ce n'est généralement pas un problème d'accéder à un enregistreur (ou à l'enregistreur racine, pour être précis) en tant que singleton, via une méthode statique getInstance() ou getRootLogger(). (sauf si vous voulez voir ce qui est enregistré par la classe que vous testez - mais d'après mon expérience, je peux à peine me rappeler de tels cas où cela était nécessaire. Là encore, pour d'autres, cela pourrait être une préoccupation plus urgente).

IMO généralement un enregistreur singleton n'est pas un souci, car il ne contient aucun état pertinent pour la classe que vous testez. Autrement dit, l'état de l'enregistreur (et ses modifications possibles) n'ont aucun effet sur l'état de la classe testée. Cela ne rend donc pas vos tests unitaires plus difficiles.

L'alternative serait d'injecter l'enregistreur via le constructeur, à (presque) chaque classe de votre application. Pour la cohérence des interfaces, il devrait être injecté même si la classe en question ne consigne rien actuellement - l'alternative serait que lorsque vous découvrez à un moment donné que maintenant vous devez enregistrer quelque chose de cette classe, vous avez besoin d'un enregistreur, donc vous devez ajouter un paramètre constructeur pour DI, cassant tout le code client. Je n'aime pas ces deux options, et je pense que l'utilisation de DI pour la journalisation serait simplement compliquer ma vie afin de se conformer à une règle théorique, sans aucun avantage concret.

Donc ma ligne de fond est: ne classe qui est utilisée (presque) universellement, mais ne contient pas d'état pertinent pour votre application, peut être implémentée en toute sécurité en tant que Singleton.

17
Péter Török

Il s'agit principalement, mais pas entièrement, de tests. Les singltons étaient populaires parce qu'il était facile de les consommer, mais il y a un certain nombre d'inconvénients aux singletons.

  • Difficile à tester. Ce qui signifie comment m'assurer que l'enregistreur fait la bonne chose.
  • Difficile de tester avec. Ce qui signifie que si je teste du code qui utilise l'enregistreur, mais que ce n'est pas l'objet de mon test, je dois encore m'assurer que mon test test prend en charge l'enregistreur
  • Parfois, vous ne voulez pas de singlton, mais plus de flexibilité

DI vous donne la consommation facile de vos classes dépendantes - il suffit de le mettre dans les arguments du constructeur, et le système vous le fournit - tout en vous donnant la flexibilité de test et de construction.

9
Scott Weinstein

La seule fois où vous devez utiliser un Singleton au lieu de l'injection de dépendance est si le Singleton représente une valeur immuable, telle que List.Empty ou similaire (en supposant des listes immuables).

La vérification de l'intestin pour un singleton devrait être "serais-je d'accord si c'était une variable globale au lieu d'un singleton?" Sinon, vous utilisez le modèle Singleton pour appliquer une variable globale et devez envisager une approche différente.

2
kyoryu

Je viens de consulter l'article Monostate - c'est une alternative astucieuse à Singleton, mais il a des propriétés étranges:

class Mono{
    public static $db;
    public function setDb($db){
       self::$db = $db;
    }

}

class Mapper extends Mono{
    //mapping procedure
    return $Entity;

    public function save($Entity);//requires database connection to be set
}

class Entity{
public function save(){
    $Mapper = new Mapper();
    $Mapper->save($this);//has same static reference to database class     
}

$Mapper = new Mapper();
$Mapper->setDb($db);

$User = $Mapper->find(1);
$User->save();

N'est-ce pas effrayant - car le mappeur dépend vraiment de la connexion à la base de données pour effectuer save () - mais si un autre mappeur a été créé auparavant - il peut ignorer cette étape lors de l'acquisition de ses dépendances. Bien que soigné, c'est aussi un peu désordonné, non?

1
sunwukung
0
topright gamedev