Je sais que c'est un débat brûlant et les opinions ont tendance à changer avec le temps quant à la meilleure pratique d'approche.
J'avais l'habitude d'utiliser exclusivement l'injection de terrain pour mes cours, jusqu'à ce que je commence à lire sur différents blogs (exs: petrikainulainen et schauderhaft et fowler ) à propos de les avantages de l'injection de constructeur. J'ai depuis changé mes méthodologies pour utiliser l'injection de constructeur pour les dépendances requises et l'injection de setter pour les dépendances facultatives.
Cependant, j'ai récemment entamé un débat avec l'auteur de JMockit - un cadre moqueur - dans lequel il voit l'injection de constructeur et de setter comme une mauvaise pratique et indique que la communauté JEE est d'accord avec lui.
Dans le monde d'aujourd'hui, existe-t-il une façon préférée de faire l'injection? L'injection sur le terrain est-elle préférée?
Après être passé à l'injection de constructeur par injection de terrain au cours des deux dernières années, je le trouve beaucoup plus clair à utiliser, mais je me demande si je devrais réexaminer mon point de vue. L'auteur de JMockit (Rogério Liesenfeld) , est clairement bien versé en DI, donc je me sens obligé de revoir mon approche étant donné qu'il se sent si fortement contre l'injection constructeur/setter.
L'injection sur le terrain est un peu trop "action fantasmagorique à distance" à mon goût.
Prenons l'exemple que vous avez fourni dans votre message Google Groupes:
public class VeracodeServiceImplTest {
@Tested(fullyInitialized=true)
VeracodeServiceImpl veracodeService;
@Tested(fullyInitialized=true, availableDuringSetup=true)
VeracodeRepositoryImpl veracodeRepository;
@Injectable private ResultsAPIWrapper resultsApiWrapper;
@Injectable private AdminAPIWrapper adminApiWrapper;
@Injectable private UploadAPIWrapper uploadApiWrapper;
@Injectable private MitigationAPIWrapper mitigationApiWrapper;
static { VeracodeRepositoryImpl.class.getName(); }
...
}
Donc, fondamentalement, ce que vous dites est que "j'ai cette classe avec un état privé, à laquelle j'ai attaché @injectable
annotations, ce qui signifie que l'état peut être rempli automatiquement par un agent de l'extérieur, même si mon état a tous été déclaré privé. "
Je comprends les motivations de cela. C'est une tentative d'éviter une grande partie de la cérémonie qui est inhérente à la mise en place d'une classe correctement. Fondamentalement, ce que l'on dit est que "je suis fatigué d'écrire tout ce passe-partout, donc je vais juste annoter tout mon état, et laisser le conteneur DI se charger de le régler pour moi."
C'est un point de vue parfaitement valable. Mais c'est aussi une solution de contournement pour les fonctionnalités de langage qui ne devraient sans doute pas être contournées. Et pourquoi s'arrêter là? Traditionnellement, DI a compté sur chaque classe ayant une interface compagnon. Pourquoi ne pas également éliminer toutes ces interfaces avec des annotations?
Considérez l'alternative (cela va être C #, parce que je le connais mieux, mais il y a probablement un équivalent exact en Java):
public class VeracodeService
{
private readonly IResultsAPIWrapper _resultsApiWrapper;
private readonly IAdminAPIWrapper _adminApiWrapper;
private readonly IUploadAPIWrapper _uploadApiWrapper;
private readonly IMitigationAPIWrapper _mitigationApiWrapper;
// Constructor
public VeracodeService(IResultsAPIWrapper resultsApiWrapper, IAdminAPIWrapper adminApiWrapper, IUploadAPIWrapper uploadApiWrapper, IMitigationAPIWrapper mitigationApiWrapper)
{
_resultsAPIWrapper = resultsAPIWrapper;
_adminAPIWrapper = adminAPIWrapper;
_uploadAPIWrapper = uploadAPIWrapper;
_mitigationAPIWrapper = mitigationAPIWrapper;
}
}
Je connais déjà des choses sur ce cours. C'est une classe immuable; l'état ne peut être défini que dans le constructeur (références, dans ce cas particulier). Et parce que tout dérive d'une interface, je peux échanger des implémentations dans le constructeur, c'est là que vos maquettes entrent en jeu.
Maintenant, tout ce que mon conteneur DI a à faire est de réfléchir sur le constructeur pour déterminer les objets dont il a besoin pour se renouveler. Mais cette réflexion se fait sur un membre public, d'une manière de première classe; c'est-à-dire que les métadonnées font déjà partie de la classe, ayant été déclarées dans le constructeur, ne méthode dont le but exprès est de fournir à la classe les dépendances dont elle a besoin.
Certes, c'est beaucoup de passe-partout, mais c'est ainsi que le langage a été conçu. Les annotations semblent être un sale hack pour quelque chose qui aurait dû être intégré dans la langue elle-même.
L'argument de la plaque de test d'initialisation avec moins de tests est valide, mais il y a d'autres problèmes à prendre en compte. Tout d'abord, vous devez répondre à une question:
Est-ce que je veux que ma classe ne soit instanciable qu'avec réflexion?
Utiliser des injections de champ signifie restreindre compatibilité d'une classe aux environnements d'injection de dépendance qui instancient des objets en utilisant la réflexion et prennent en charge ces annotations d'injection particulières. Certaines plates-formes basées sur Java ne prend même pas en charge la réflexion ( GWT ), donc la classe à injection de champ ne sera pas compatible avec eux.
Le deuxième problème est performance . Un appel constructeur (direct ou par réflexion) est toujours plus rapide qu'un tas d'affectations de champs de réflexion. Les frameworks d'injection de dépendances doivent utiliser l'analyse de réflexion pour construire un arbre de dépendance et créer des constructeurs de réflexion. Ce processus entraîne un impact supplémentaire sur les performances.
Les performances affectent maintenabilité . Si chaque suite de tests doit être exécutée dans une sorte de conteneur d'injection de dépendance, une exécution de quelques milliers de tests unitaires peut durer des dizaines de minutes. Selon la taille d'une base de code, cela peut être un problème.
Tout cela soulève de nombreuses nouvelles questions:
En règle générale, plus un projet est grand et important, plus ces facteurs sont importants. En outre, en termes de qualité, nous aimerions généralement garder le code élevé en termes de compatibilité, de testabilité et de maintenabilité. Du point de vue philosophique, l'injection de champs casse encapsulation , qui est l'un des quatre principes fondamentaux de programmation orientée objet , qui est le principal paradigme de Java.
Beaucoup, beaucoup d'arguments contre l'injection sur le terrain.
L'injection sur le terrain obtient un "non" définitif de ma part.
Comme Robert Harvey, c'est un peu trop automagique à mon goût. Je préfère le code explicite à l'implicite, et je ne tolère l'indirection que si/quand elle offre des avantages clairs car elle rend le code plus difficile à comprendre et à raisonner.
Comme Maciej Chałapuk, je n'aime pas avoir besoin de réflexion ou d'un framework DI/IoC pour instancier une classe. C'est comme conduire à Rome pour se rendre à Paris depuis Amsterdam.
Attention, j'aime DI et tous les avantages qu'elle apporte. Je ne suis pas sûr d'avoir besoin des frameworks pour instancier une classe. Surtout pas l'effet que les conteneurs IoC ou autres frameworks DI peuvent avoir sur le code de test.
J'aime que mes tests soient très simples. Je n'aime pas avoir à passer par l'indirection de la mise en place d'un conteneur IoC ou d'un autre framework DI pour ensuite les configurer ma classe sous test. C'est juste gênant.
Et cela rend mes tests dépendants de l'état global du framework. C'est-à-dire que je ne peux plus exécuter deux tests en parallèle qui nécessitent la mise en place d'une instance de classe X avec différentes dépendances.
Au moins avec l'injection de constructeur et de setter, vous avez la possibilité de ne pas utiliser les frameworks et/ou la réflexion dans vos tests.
Eh bien, cela semble une discussion polémique. La première chose qui doit être adressée est que L'injection de champ est différente de l'injection Setter . Je travaille déjà avec certaines personnes qui pensent que l'injection Field et Setter est la même chose.
Donc, pour être clair:
Injection sur le terrain:
@Autowired
private SomeService someService;
Setter injection:
@Autowired
public void setSomeService(SomeService someService) {
this.someService = someService;
}
Mais, pour moi, les deux partagent les mêmes préoccupations.
Et, mon préféré, l'injection constructeur:
@AutoWired
public MyService(SomeService someService) {
this.someService = someService;
}
J'ai les raisons suivantes de croire que l'injection de constructeur est meilleure que l'injection setter/field (je citerai quelques liens Spring pour démontrer mon point):
@AutoWired
réparti entre votre code, votre code est moins dépendant du framework. Je sais que la possibilité de changer simplement le cadre DI dans un projet ne justifie pas cela (qui fait ça, non?). Mais cela montre, pour moi, un meilleur code (j'ai appris cela à la dure avec EJB et recherches). Et avec Spring, vous pouvez même supprimer le @AutoWired
annotation utilisant l'injection de constructeur.Si vous recherchez plus d'informations, je recommande cet ancien article (mais toujours pertinent) de Spring Blog nous expliquant pourquoi ils utilisent autant l'injection de setter et la recommandation d'utiliser l'injection de constructeur:
l'injection de setter est utilisée beaucoup plus souvent que vous ne le pensez, c'est le fait que les frameworks comme Spring en général, sont beaucoup plus adaptés pour être configurés par injection de setter que par injection de constructeur
Et cette note expliquant pourquoi ils croient que le constructeur convient mieux au code d'application:
Je pense que l'injection de constructeur est beaucoup plus utilisable pour le code d'application que pour le code framework. Dans le code d'application, vous avez intrinsèquement moins besoin de valeurs facultatives que vous devez configurer
Si vous n'êtes pas vraiment sûr de l'avantage du constructeur, peut-être l'idée de mélanger le setter (pas le champ) avec l'injection du constructeur est une option, comme l'explique le Spring team :
Étant donné que vous pouvez mélanger les DI basés sur le constructeur et sur le Setter, il est préférable d'utiliser des arguments de constructeur pour les dépendances obligatoires et des paramètres pour les dépendances facultatives.
Mais rappelez-vous que l'injection sur le terrain est probablement celle à éviter parmi les trois.