Qu'est-ce que l'injection de dépendances ?

Plusieurs questions ont déjà été postées avec des questions spécifiques sur l'[injection de dépendance][1], comme quand l'utiliser et quels frameworks existent pour cela. Cependant,

Qu'est-ce que l'injection de dépendances et quand/pourquoi doit-on ou ne doit-on pas l'utiliser?

[1] : http://en.wikipedia.org/wiki/Dependency_injection

La meilleure définition que j'ai trouvée jusqu'à présent est une définition de James Shore :

"Dependency Injection" est un terme à 25 dollars terme pour un concept à 5 cents. [...] L'injection de dépendances consiste à donner à un objet ses variables d'instance. [...].

Il existe un article de Martin Fowler qui peut également s'avérer utile.

L'injection de dépendances consiste essentiellement à fournir les objets dont un objet a besoin (ses dépendances) au lieu de les lui faire construire lui-même. Il s'agit d'une technique très utile pour les tests, car elle permet de simuler les dépendances.

Les dépendances peuvent être injectées dans les objets par de nombreux moyens (comme l'injection de constructeur ou l'injection de setter). On peut même utiliser des cadres d'injection de dépendances spécialisés (par exemple Spring) pour le faire, mais ils ne sont certainement pas nécessaires. Vous n'avez pas besoin de ces frameworks pour disposer de l'injection de dépendances. Instancier et passer des objets (dépendances) explicitement est une injection tout aussi bonne que l'injection par framework.

Commentaires (6)
Solution

L'injection de dépendances consiste à transmettre des dépendances à d'autres objets ou à un framework( injecteur de dépendances).

L'injection de dépendances facilite les tests. L'injection peut se faire par le biais du constructeur.

Le constructeur de SomeClass() est le suivant :

public SomeClass() {
    myObject = Factory.getObject();
}

Problème : Si myObject implique des tâches complexes comme l'accès au disque ou au réseau, il est difficile de faire des tests unitaires sur SomeClass(). Les programmeurs doivent simuler myObject et peuvent intercepter l'appel à la fabrique.

Solution alternative :

  • Passer myObject en tant qu'argument au constructeur
public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

myObject peut être passé directement ce qui facilite les tests.

  • Une alternative courante est de définir un constructeur do-nothing. L'injection de dépendance peut se faire par le biais de setters. (h/t @MikeVella).
  • Martin Fowler][1] documente une troisième alternative (h/t @MarcDix), où les classes implémentent explicitement une interface pour les dépendances que les programmeurs souhaitent injecter.

Il est plus difficile d'isoler les composants dans les tests unitaires sans injection de dépendances.

En 2013, lorsque j'ai écrit cette réponse, c'était un thème majeur sur le [Google Testing Blog][2]. Cela reste le plus grand avantage pour moi, car les programmeurs n'ont pas toujours besoin de la flexibilité supplémentaire dans leur conception d'exécution (par exemple, pour le localisateur de services ou des modèles similaires). Les programmeurs ont souvent besoin d'isoler les classes pendant les tests.

[1] : http://martinfowler.com/articles/injection.html#InterfaceInjection [2] : http://googletesting.blogspot.com/

Commentaires (10)

L'injection de dépendances est une pratique selon laquelle les objets sont conçus de manière à recevoir des instances d'objets provenant d'autres parties du code, au lieu de les construire en interne. Cela signifie que tout objet implémentant l'interface requise par l'objet peut être remplacé sans modifier le code, ce qui simplifie les tests et améliore le découplage.

Par exemple, considérez ces clases :

public class PersonService {
  public void addManager( Person employee, Person newManager ) { ... }
  public void removeManager( Person employee, Person oldManager ) { ... }
  public Group getGroupByManager( Person manager ) { ... }
}

public class GroupMembershipService() {
  public void addPersonToGroup( Person person, Group group ) { ... }
  public void removePersonFromGroup( Person person, Group group ) { ... }
} 

Dans cet exemple, l'implémentation de PersonService::addManager et PersonService::removeManager aurait besoin d'une instance du GroupMembershipService afin de faire son travail. Sans l'injection de dépendances, la manière traditionnelle de faire cela serait d'instancier un nouveau GroupMembershipService dans le constructeur de PersonService et d'utiliser l'attribut de cette instance dans les deux fonctions. Cependant, si le constructeur de GroupMembershipService requiert plusieurs choses, ou pire encore, s'il y a des "setters" d'initialisation qui doivent être appelés sur le GroupMembershipService, le code se développe assez rapidement, et le PersonService dépend maintenant non seulement du GroupMembershipService mais aussi de tout ce dont dépend le GroupMembershipService. De plus, le lien vers GroupMembershipService est codé en dur dans le PersonService, ce qui signifie que vous ne pouvez pas "faconner" un GroupMembershipService à des fins de test, ou pour utiliser un modèle de stratégie dans différentes parties de votre application.

Avec l'injection de dépendance, au lieu d'instancier le GroupMembershipService dans votre PersonService, vous pouvez soit le passer au constructeur du PersonService, soit ajouter une propriété (getter et setter) pour définir une instance locale de celui-ci. Cela signifie que votre PersonService n'a plus à se soucier de la façon de créer un GroupMembershipService, il accepte simplement ceux qui lui sont donnés, et travaille avec eux. Cela signifie également que tout ce qui est une sous-classe de GroupMembershipService, ou implémente l'interface GroupMembershipService peut être "injecté" dans le PersonService, et le PersonService n'a pas besoin d'être informé de ce changement.

Commentaires (3)