Comment utiliser l'injection de dépendance en conjonction avec le modèle d'usine

10

Considérons un module qui est responsable de l'analyse des fichiers de tout type donné. Je pense utiliser le modèle de stratégie pour résoudre ce problème comme je l'ai déjà expliqué ici . Veuillez vous référer au message lié avant de poursuivre avec cette question.

Considérez la classe B qui a besoin du contenu du fichier product.xml. Cette classe devra instancier l'implémenteur concret approprié de l'interface Parser pour analyser le fichier XML. Je peux déléguer l'instanciation de l'implémenteur concret approprié à une usine telle que la classe B a une usine. Cependant, la classe B "dépendra" alors d'une Factory pour instancier l'implémenteur concret. Cela signifie que le constructeur ou une méthode de définition dans la classe B devra passer la Factory.

Par conséquent, l'usine et la classe B qui doivent analyser un fichier seront étroitement couplées. Je comprends que je peux me tromper complètement sur tout ce que j'ai expliqué jusqu'à présent. Je voudrais savoir si je peux utiliser l'injection de dépendance dans un scénario où la dépendance à injecter est une usine et quelle serait la bonne façon de l'implémenter afin que je puisse profiter de domaines tels que se moquer de l'usine dans mes tests unitaires.

CKing
la source

Réponses:

8

La bonne façon de le faire est de dépendre d'une interface, puis d'injecter une implémentation de cette interface dans la classe B.

Une interface est à peu près la chose la plus mince sur laquelle vous pouvez compter - je la compare à essayer d'attraper un brin de fumée. Le code doit être couplé à quelque chose, sinon il ne fera rien, mais le couplage à une interface est à peu près aussi découplé que possible, mais les interfaces peuvent fournir toutes les fonctionnalités que vous pourriez souhaiter.

Demandez donc au constructeur de classe B de prendre une interface avec la classe dont il a besoin et de demander à l'usine de produire cette classe en tant qu'implémenteur de l'interface. Ne dépendez pas de l'usine, dépendez de l'interface et demandez à l'usine de fournir une implémentation de cette usine.

Alors oui, vous utiliserez l'injection de dépendance, mais il n'y a rien de mal à cela. L'injection de dépendances - en particulier une injection de constructeur simple - devrait être la façon "normale" de faire les choses. Mettez simplement les nouvelles choses aussi loin que possible dans votre application (et aussi près de la première ligne de main) que possible, et masquez le nouvel appel dans une classe conçue spécifiquement pour créer des choses.

Conclusion: n'hésitez pas à injecter des dépendances. Cela devrait être la façon normale de faire les choses.

Nick Hodges
la source
L'injection de constructeur retarde-t-elle les nouveautés le plus longtemps possible?
JeffO
C'était mal placé - je l'ai corrigé pour dire "aussi loin que possible dans votre application".
Nick Hodges
Oui. Je pensais à dépendre de l'interface Parser au lieu d'une usine. Maintenant, si une autre classe utilise la classe B, elle devra dépendre de l'interface dont dépend la classe B. Cela continuera à se poursuivre jusqu'à la classe de haut niveau. Cette classe de niveau supérieur devra enfin utiliser une fabrique pour instancier l'instance concrète appropriée de l'analyseur. La classe de niveau supérieur doit-elle dépendre d'une usine ou doit-elle utiliser l'usine directement dans ses méthodes?
CKing
1
Bien dit:
n'hésitez
9

Je pense que votre prémisse est un peu confuse ici, vous parlez d'injecter une usine, mais le modèle d'usine est un modèle de création dont le but était de faire un sous-ensemble de ce que fait un cadre d'injection de dépendance, lorsque les cadres DI n'étaient pas répandus, ce modèle était utile pour cette raison. Cependant, si vous avez un cadre DI, vous n'avez plus vraiment besoin d'une usine car le cadre DI peut remplir le but que l'usine aurait atteint.

Cela dit, permettez-moi d'expliquer un peu l'injection de dépendance et comment vous l'utiliseriez généralement.

Il existe différentes manières de faire l'injection de dépendances, mais les plus courantes sont l'injection de constructeur, l'injection de propriété et le DIContainer direct. Je vais parler de l'injection de constructeur car l'injection de propriété est la mauvaise approche la plupart du temps (la bonne approche parfois) et l'accès DIContainer n'est pas préférable, sauf lorsque vous ne pouvez absolument pas faire l'une des autres approches.

L'injection de constructeur est l'endroit où vous avez l'interface pour une dépendance et un DIContainer (ou fabrique) qui connaît l'implémentation concrète pour cette dépendance, et où que vous ayez besoin d'un objet qui dépend de cette interface, au moment de la construction, vous remettez l'implémentation de l'usine à il.

c'est à dire

IDbConnectionProvider connProvider = DIContainer.Get<IDbConnectionProvider>();
IUserRepository userRepo = new UserRepository(connProvider);
User currentUser = userRepo.GetCurrentUser();

De nombreux frameworks DI peuvent simplifier cela de manière significative à l'endroit où votre DIContainer inspectera le constructeur de UserRepository pour les interfaces pour lesquelles il connaît des implémentations concrètes, et leur remettra automatiquement celles-ci pour vous; cette technique est souvent appelée inversion de contrôle, bien que DI et IoC soient tous deux des termes qui s'échangent beaucoup et ont des différences vagues (le cas échéant).

Maintenant, si vous vous demandez comment le code global accède au DIContainer, vous pouvez avoir une classe statique pour y accéder ou ce qui est plus approprié est que la plupart des frameworks DI vous permettent de créer un DIContainer, dans lequel il se comportera comme un wrapper à un dictionnaire singleton interne pour les types qu'il sait être concrets pour des interfaces données.

Cela signifie que vous pouvez renouveler le DIContainer où vous voulez dans le code et obtenir effectivement le même DIContainer que vous aviez déjà configuré pour connaître vos relations interface-à-béton. Le moyen habituel de cacher le DIContainer des parties du code qui ne devraient pas interagir directement avec lui est simplement de s'assurer que le ou les projets nécessaires ont une référence au framework DI.

Jimmy Hoffa
la source
Je ne suis pas entièrement d'accord avec le premier paragraphe. Il existe encore des scénarios où vous dépendez d'une usine (implémentant une interface) qui est injectée par le conteneur DI.
Piotr Perak
Je pense que vous avez manqué le point puisque l'OP n'a pas parlé des cadres DI ou des conteneurs DI.
Doc Brown
@Jimmy Hoffa Bien que vous ayez soulevé des points très intéressants, je connais les conteneurs IoC tels que Spring et le concept de l'injection de dépendances. Ma préoccupation concernait un scénario dans lequel vous n'utilisez pas de framework IoC, auquel cas, vous devez écrire une classe qui instanciera vos dépendances pour vous. Si la classe A dépend de l'interface B et que la classe C utilise la classe A, alors la classe C dépend de l'interface B. Quelqu'un doit donner à la classe C une classe B ce. Si la classe C dépend alors de cla
CKing
Si la classe A dépend de l'interface B et que la classe C utilise la classe A, alors la classe C dépend de l'interface B. Quelqu'un doit donner à la classe C une interface B. La classe C doit-elle alors dépendre de l'interface B ou doit-elle dépendre de la classe qui a instancié le dépendances Ie l'usine. Vous semblez avoir répondu à cette question dans votre réponse mais avec une surcharge d'informations :)
CKing
@bot comme je l'ai dit dans la préface, vous pouvez en grande partie traiter les usines comme un sous-ensemble des fonctionnalités d'un conteneur DI, ce n'est pas vrai dans tous les scénarios, comme Doc Brown l'a mentionné, mais regardez mon exemple de code et remplacez DIContainerpar DbConnectionFactoryet le concept Il est toujours possible de récupérer la mise en œuvre concrète de votre DI / Factory / etc et de la remettre aux consommateurs du type au moment de la construction.
Jimmy Hoffa
5

Vous pouvez passer une usine via l'injection de dépendances comme vous passez n'importe quoi d'autre, ne laissez pas la récursivité de la situation vous dérouter. Je ne sais pas quoi dire d'autre sur sa mise en œuvre - vous savez déjà comment faire l'injection de dépendance.

J'utilise DI pour injecter des usines assez régulièrement.

psr
la source
1
Si une classe dépend d'une usine, vous devrez vous moquer de l'usine lors du test d'unité de cette classe. Comment procédez-vous? Laisser la classe dépendre d'une interface d'usine?
CKing
4

Il n'y a rien de mal à injecter des usines. C'est un moyen standard si vous ne pouvez pas décider pendant la construction de l'objet «parent» de quel type de dépendance vous aurez besoin. Je pense que l'exemple l'expliquera mieux. J'utiliserai c # car je ne connais pas java.


class Parent
{
    private IParserFactory parserFactory;
    public Parent(IParserFactory factory)
    {
        parserFactory = factory
    }

    public ParsedObject ParseFrom(string filename, FileType fileType)
    {
        var parser = parserFactory.CreateFor(fileType);
        return parser.Parse(filename);
    }
}
Piotr Perak
la source