Quelle est la différence pratique entre les styles d'injection de dépendance?

12

Je suis nouveau dans l' injection de dépendances et j'ai quelques questions sur le style à utiliser dans mes applications. Je viens de lire Inversion of Control Containers et le Dependency Injection pattern de Martin Fowler, mais je ne peux pas faire la différence pratique entre le constructeur, le setter et l'injection d'interface.

Il me semble que les raisons d'utiliser l'un sur l'autre ne sont qu'une question de nettoyage et / ou de clarté du code. Quelle est la différence? Y a-t-il des avantages ou des inconvénients importants à utiliser l'un par rapport à l'autre, ou est-ce juste ce que j'ai déclaré auparavant?

À mon avis, l' injection de constructeur est la plus intuitive de toutes, ainsi que l' injection d'interface est la moins. D'un autre côté, l' injection de setter est un moyen terme, mais êtes-vous censé pouvoir changer l'instance de l'objet de dépendance que vous avez initialement injecté? Ce style d'injection garantit-il que l'objet qui a besoin de la dépendance le fera toujours injecter? Je ne crois pas, mais veuillez me corriger si je me trompe.

ecampver
la source
Je ne sais pas quoi d'autre vous avez trouvé ou lu en cours de route. J'ai trouvé des fils similaires ici et ici . Je suis moi-même nouveau et serais curieux de connaître les réponses que vous pourriez obtenir :)
@ user1766760 vous devriez m'aider alors ici, ma question est en cours de vote pour être fermée, deux votes de plus et c'est fait !!! Votez la question ou quelque chose, je ne sais pas ce qui peut être fait pour éviter de fermer.
ecampver
euh ... je suis nouveau à SO donc je ne sais pas trop comment je peux aider / pourquoi il est voté pour fermer (je ne vois aucune indication ??). Si j'ose deviner, ce n'est peut-être pas une question de programmation spécifique mais plutôt une discussion?
Vous voudrez peut-être développer un peu la question. L'injection de dépendance est une forme d '"inversion de contrôle". Il existe des alternatives. Par exemple, l'emplacement du service est une alternative utile mais mûre pour les abus.
Ian

Réponses:

12

L'injection de constructeur a l'avantage de rendre la dépendance explicite et d'obliger le client à fournir une instance. Il peut également garantir que le client ne pourra pas modifier l'instance ultérieurement. Un inconvénient (possible) est que vous devez ajouter un paramètre à votre constructeur.

Setter Injection a l'avantage de ne pas nécessiter l'ajout d'un paramètre au constructeur. Il ne nécessite pas non plus que le client définisse l'instance. Ceci est utile pour les dépendances facultatives. Cela peut également être utile si vous souhaitez que la classe crée, par exemple, un référentiel de données réel par défaut, puis dans un test, vous pouvez utiliser le setter pour le remplacer par une instance de test.

L'injection d'interface , pour autant que je sache, n'est pas très différente de l'injection de setter. Dans les deux cas, vous définissez (en option) une dépendance qui peut être modifiée ultérieurement.

En fin de compte, c'est une question de préférence et de savoir si une dépendance est requise ou non . Personnellement, j'utilise l'injection constructeur presque exclusivement. J'aime que cela rend les dépendances d'une classe explicites en forçant le client à fournir une instance dans le constructeur. J'aime aussi que le client ne puisse pas changer l'instance après coup.

Souvent, ma seule raison de passer dans deux implémentations distinctes est pour les tests. En production, je peux réussir un DataRepository, mais en test, je passerais un FakeDataRepository. Dans ce cas, je fournirai généralement deux constructeurs: un sans paramètres et un autre qui accepte a IDataRepository. Ensuite, dans le constructeur sans paramètres, je vais enchaîner un appel au deuxième constructeur et passer un new DataRepository().

Voici un exemple en C #:


public class Foo
{
  private readonly IDataRepository dataRepository;

  public Foo() : this(new DataRepository())
  {
  }

  public Foo(IDataRespository dataRepository)
  {
    this.dataRepository = dataRepository;
  }
}

C'est ce qu'on appelle l'injection de dépendance du pauvre. Je l'aime parce que dans le code client de production, je n'ai pas besoin de me répéter en ayant plusieurs instructions répétées qui ressemblent à

var foo = new Foo(new DataRepository());
Cependant, je peux toujours passer dans une autre implémentation pour les tests. Je me rends compte qu'avec Poor Man's DI, je coder en dur ma dépendance, mais c'est acceptable pour moi car j'utilise principalement DI pour les tests.

jhewlett
la source
merci, c'est assez proche de ce que je comprends, mais encore, y a-t-il un scénario où vous ne pouvez pas utiliser l' injection de constructeur ?
ecampver
1
Vous ne voudriez probablement pas utiliser l'injection de constructeur pour les dépendances facultatives. Je ne peux penser à aucune autre raison de ne pas l'utiliser.
jhewlett
J'utilise l'injection de setter de propriétés spécifiquement pour remplir certaines classes de configuration qui incluent un grand nombre de valeurs. Je ne l'utilise nulle part ailleurs. Je suis toujours un peu dubitatif quant à la justification de paramètres facultatifs, car il y a de fortes chances que ce soit une infraction à la règle de la responsabilité unique. Bien sûr, les règles sont censées être enfreintes, alors ...
Ian
@jhewlett - c'est fantastique, je cherchais une bonne technique de stubbing simple pour les tests unitaires qui ne complique pas trop ma solution - et je pense que c'est ça :)
Rocklan
2

Les différences entre l'injection de constructeur et de setter sont déjà décrites de manière adéquate ci-dessus, donc je ne les développerai pas davantage.

L'injection d'interface est une forme d'injection plus avancée qui est utile car elle permet de décider de la dépendance au moment de son utilisation plutôt que lors de l'initialisation de l'objet qui l'utilisera. Cela permet un certain nombre d'options utiles:

  • La dépendance peut être définie différemment de l'objet dans lequel elle est injectée; par exemple, vous pouvez utiliser l'injection d'interface pour fournir un objet pour lequel il existe par session utilisateur ou par thread à un singleton global. Chaque fois que l'objet a besoin de la dépendance, il appellera la méthode getter fournie par le framework, ce qui peut renvoyer des résultats différents selon la situation dans laquelle il est appelé.

  • Il permet une initialisation paresseuse - il n'est pas nécessaire d'initialiser la dépendance jusqu'à ce qu'elle soit sur le point d'être utilisée

  • Il permet aux dépendances d'être chargées à partir d'une copie mise en cache lorsqu'elles existent ou réinitialisées lorsqu'elles n'existent pas (par exemple en utilisant un SoftReferenceen Java).

De toute évidence, des techniques avancées comme celle-ci ont des inconvénients; dans ce cas, le problème principal est que le code devient moins clair (les classes utilisées dans votre code deviennent abstraites, et il n'y a pas d'implémentation concrète évidente, ce qui peut être déroutant si vous n'y êtes pas habitué) et vous devenez plus dépendant sur votre framework d'injection de dépendances (il est toujours possible d'instancier vos objets manuellement, bien sûr, mais c'est plus difficile qu'avec d'autres styles d'injection).

Jules
la source