Pourquoi utilise-t-on l'injection de dépendance?

536

J'essaie de comprendre les injections de dépendance (DI), et encore une fois j'ai échoué. Cela semble juste idiot. Mon code n'est jamais un gâchis; J'écris à peine des fonctions et des interfaces virtuelles (bien que je le fasse une fois dans une lune bleue) et toute ma configuration est magiquement sérialisée en classe en utilisant json.net (parfois en utilisant un sérialiseur XML).

Je ne comprends pas très bien quel problème cela résout. Cela ressemble à une façon de dire: "salut. Lorsque vous exécutez cette fonction, renvoyez un objet de ce type et utilise ces paramètres / données."
Mais ... pourquoi devrais-je jamais l'utiliser? Remarque Je n'ai jamais eu besoin de l'utiliser objectaussi, mais je comprends à quoi ça sert.

Quelles sont les situations réelles dans la construction d'un site Web ou d'une application de bureau où l'on utiliserait DI? Je peux facilement trouver des cas pour lesquels quelqu'un peut vouloir utiliser des interfaces / fonctions virtuelles dans un jeu, mais il est extrêmement rare (assez rare que je ne me souvienne pas d'une seule instance) d'utiliser cela dans du code non-jeu.

TJ Crowder
la source
3
Ces informations peuvent également être utiles: martinfowler.com/articles/injection.html
ta.speot.is
1
Voir aussi .
BlueRaja - Danny Pflughoeft
41
Je suis avec ce type: jamesshore.com/Blog/Dependency-Injection-Demystified.html .
Eric Lippert
5
Une autre explication très simple de DI: codearsenal.net/2015/03/…
ybonda

Réponses:

840

Tout d'abord, je veux expliquer une hypothèse que je fais pour cette réponse. Ce n'est pas toujours vrai, mais assez souvent:

Les interfaces sont des adjectifs; les classes sont des noms.

(En fait, il y a aussi des interfaces qui sont des noms, mais je veux généraliser ici.)

Ainsi, par exemple, une interface peut être quelque chose comme IDisposable, IEnumerableou IPrintable. Une classe est une implémentation réelle d'une ou plusieurs de ces interfaces: Listou Mappeut toutes deux être des implémentations de IEnumerable.

Pour comprendre: souvent, vos classes dépendent les unes des autres. Par exemple, vous pourriez avoir une Databaseclasse qui accède à votre base de données (hah, surprise! ;-)), mais vous voulez également que cette classe fasse la journalisation sur l'accès à la base de données. Supposons que vous ayez une autre classe Logger, puis Databaseune dépendance à Logger.

Jusqu'ici tout va bien.

Vous pouvez modéliser cette dépendance à l'intérieur de votre Databaseclasse avec la ligne suivante:

var logger = new Logger();

et tout va bien. C'est bien jusqu'au jour où vous réalisez que vous avez besoin d'un tas d'enregistreurs: Parfois, vous voulez vous connecter à la console, parfois au système de fichiers, parfois en utilisant TCP / IP et un serveur de journalisation à distance, et ainsi de suite ...

Et bien sûr, vous ne voulez PAS changer tout votre code (en attendant vous en avez des milliers) et remplacer toutes les lignes

var logger = new Logger();

par:

var logger = new TcpLogger();

Tout d'abord, ce n'est pas amusant. Deuxièmement, cela est sujet aux erreurs. Troisièmement, c'est un travail stupide et répétitif pour un singe entraîné. Donc que fais-tu?

Évidemment, c'est une assez bonne idée d'introduire une interface ICanLog(ou similaire) qui est implémentée par tous les différents enregistreurs. L'étape 1 de votre code consiste donc à:

ICanLog logger = new Logger();

Maintenant que l'inférence de type ne change plus de type, vous avez toujours une seule interface pour vous développer. L'étape suivante est que vous ne voulez pas en avoir new Logger()encore et encore. Ainsi, vous mettez la fiabilité pour créer de nouvelles instances dans une seule classe d'usine centrale et vous obtenez du code tel que:

ICanLog logger = LoggerFactory.Create();

L'usine elle-même décide du type d'enregistreur à créer. Votre code ne se soucie plus, et si vous voulez changer le type d'enregistreur utilisé, vous le changez une fois : à l'intérieur de l'usine.

Maintenant, bien sûr, vous pouvez généraliser cette usine et la faire fonctionner pour tout type:

ICanLog logger = TypeFactory.Create<ICanLog>();

Quelque part, ce TypeFactory a besoin de données de configuration dont la classe réelle à instancier lorsqu'un type d'interface spécifique est demandé, vous avez donc besoin d'un mappage. Bien sûr, vous pouvez faire ce mappage dans votre code, mais un changement de type signifie alors une recompilation. Mais vous pouvez également placer ce mappage dans un fichier XML, par exemple. Cela vous permet de changer la classe réellement utilisée même après le temps de compilation (!), C'est-à-dire dynamiquement, sans recompilation!

Pour vous donner un exemple utile: pensez à un logiciel qui ne se connecte pas normalement, mais lorsque votre client appelle et demande de l'aide parce qu'il a un problème, tout ce que vous lui envoyez est un fichier de configuration XML mis à jour, et maintenant il a la journalisation est activée et votre support peut utiliser les fichiers journaux pour aider votre client.

Et maintenant, lorsque vous remplacez un peu les noms, vous vous retrouvez avec une implémentation simple d'un localisateur de service , qui est l'un des deux modèles d' inversion de contrôle (puisque vous inversez le contrôle sur qui décide de la classe exacte à instancier).

Dans l'ensemble, cela réduit les dépendances dans votre code, mais maintenant tout votre code a une dépendance au localisateur de service unique et central.

L'injection de dépendances est maintenant la prochaine étape de cette ligne: il suffit de se débarrasser de cette dépendance unique au localisateur de service: au lieu que différentes classes demandent au localisateur de service une implémentation pour une interface spécifique, vous - encore une fois - revenez au contrôle sur qui instancie quoi .

Avec l'injection de dépendances, votre Databaseclasse dispose désormais d'un constructeur qui nécessite un paramètre de type ICanLog:

public Database(ICanLog logger) { ... }

Maintenant, votre base de données a toujours un enregistreur à utiliser, mais elle ne sait plus d'où vient cet enregistreur.

Et c'est là qu'intervient un framework DI: vous configurez à nouveau vos mappages, puis demandez à votre framework DI d'instancier votre application pour vous. Comme la Applicationclasse nécessite une ICanPersistDataimplémentation, une instance de Databaseest injectée - mais pour cela, elle doit d'abord créer une instance du type d'enregistreur configuré pour ICanLog. Etc ...

Donc, pour faire court: l'injection de dépendances est l'une des deux façons de supprimer les dépendances dans votre code. Il est très utile pour les changements de configuration après la compilation, et c'est une bonne chose pour les tests unitaires (car il est très facile d'injecter des stubs et / ou des mocks).

Dans la pratique, il y a des choses que vous ne pouvez pas faire sans localisateur de service (par exemple, si vous ne savez pas à l'avance combien d'instances vous avez besoin d'une interface spécifique: un framework DI injecte toujours une seule instance par paramètre, mais vous pouvez appeler un localisateur de service à l'intérieur d'une boucle, bien sûr), donc le plus souvent chaque infrastructure DI fournit également un localisateur de service.

Mais en gros, c'est tout.

PS: Ce que j'ai décrit ici est une technique appelée injection de constructeur , il y a aussi l' injection de propriété où pas les paramètres du constructeur, mais les propriétés sont utilisées pour définir et résoudre les dépendances. Considérez l'injection de propriété comme une dépendance facultative et l'injection de constructeur comme des dépendances obligatoires. Mais la discussion à ce sujet dépasse le cadre de cette question.

Golo Roden
la source
7
Bien sûr, vous pouvez le faire de cette manière également, mais vous devez ensuite implémenter cette logique dans chaque classe, ce qui fournira un support pour l'échange de l'implémentation. Cela signifie beaucoup de code redondant et dupliqué, et cela signifie également que vous devez toucher une classe existante et la réécrire partiellement, une fois que vous décidez que vous en avez besoin maintenant. DI vous permet de l'utiliser sur n'importe quelle classe arbitraire, vous n'avez pas à les écrire de manière spéciale (sauf en définissant des dépendances en tant que paramètres dans le constructeur).
Golo Roden
138
Voici ce que je n'obtiens jamais avec DI: cela rend l'architecture beaucoup plus compliquée. Et pourtant, à mon avis, l'utilisation est assez limitée. Les exemples sont certainement toujours les mêmes: enregistreurs interchangeables, modèle / accès aux données interchangeables. Vue parfois interchangeable. Mais c'est tout. Ces quelques cas justifient-ils vraiment une architecture logicielle beaucoup plus complexe? - Divulgation complète: j'ai déjà utilisé DI avec beaucoup d'effet, mais c'était pour une architecture de plug-in très spéciale que je ne généraliserais pas.
Konrad Rudolph
17
@GoloRoden, pourquoi appelez-vous l'interface ICanLog au lieu d'ILogger? J'ai travaillé avec un autre programmeur qui faisait souvent cela, et je n'ai jamais pu comprendre la convention? Pour moi, c'est comme appeler IEnumerable ICanEnumerate?
DermFrench
28
Je l'ai appelé ICanLog, parce que nous travaillons trop souvent avec des mots (noms) qui ne veulent rien dire. Par exemple, qu'est-ce qu'un courtier? Un patron? Même un référentiel n'est pas défini de manière unique. Et avoir toutes ces choses comme noms est une maladie typique des langues OO (voir steve-yegge.blogspot.de/2006/03/… ). Ce que je veux exprimer, c'est que j'ai un composant qui peut faire la journalisation pour moi - alors pourquoi ne pas l'appeler de cette façon? Bien sûr, cela joue également avec le I en tant que première personne, d'où ICanLog (ForYou).
Golo Roden
18
@David Unit testing fonctionne très bien - après tout, une unité est indépendante des autres choses (sinon ce n'est pas une unité). Ce qui ne fonctionne pas sans conteneurs DI, c'est un test simulé. Assez honnêtement, je ne suis pas convaincu que les avantages de la moquerie l'emportent sur la complexité supplémentaire de l'ajout de conteneurs DI dans tous les cas. Je fais des tests unitaires rigoureux. Je me moque rarement.
Konrad Rudolph
499

Je pense que la plupart du temps, les gens se trompent sur la différence entre l' injection de dépendance et un cadre d' injection de dépendance (ou un conteneur comme on l'appelle souvent).

L'injection de dépendance est un concept très simple. Au lieu de ce code:

public class A {
  private B b;

  public A() {
    this.b = new B(); // A *depends on* B
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  A a = new A();
  a.DoSomeStuff();
}

vous écrivez du code comme ceci:

public class A {
  private B b;

  public A(B b) { // A now takes its dependencies as arguments
    this.b = b; // look ma, no "new"!
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  B b = new B(); // B is constructed here instead
  A a = new A(b);
  a.DoSomeStuff();
}

Et c'est tout. Sérieusement. Cela vous donne une tonne d'avantages. Deux importantes sont la possibilité de contrôler les fonctionnalités à partir d'un emplacement central (la Main()fonction) au lieu de les diffuser dans tout votre programme, et la possibilité de tester plus facilement chaque classe de manière isolée (car vous pouvez plutôt passer des simulations ou d'autres objets truqués à son constructeur d'une valeur réelle).

L'inconvénient, bien sûr, est que vous avez maintenant une méga-fonction qui connaît toutes les classes utilisées par votre programme. C'est ce avec quoi les frameworks DI peuvent aider. Mais si vous ne parvenez pas à comprendre pourquoi cette approche est utile, je vous recommande de commencer par l'injection manuelle de dépendances, afin que vous puissiez mieux comprendre ce que les différents cadres peuvent faire pour vous.

Daniel Pryden
la source
7
Pourquoi préférerais-je le deuxième code plutôt que le premier? le premier n'a que le nouveau mot-clé, comment cela aide-t-il?
user962206
17
@ user962206 réfléchissez à la façon dont vous testeriez A indépendamment de B
jk.
66
@ user962206, pensez aussi à ce qui se passerait si B avait besoin de certains paramètres dans son constructeur: pour l'instancier, A devrait connaître ces paramètres, quelque chose qui pourrait être complètement indépendant de A (il veut juste dépendre de B , pas de quoi dépend B). Passer un B déjà construit (ou n'importe quelle sous-classe ou maquette de B d'ailleurs) au constructeur de A résout cela et fait dépendre A uniquement de B :)
épidémie
17
@ acidzombie24: Comme de nombreux modèles de conception, DI n'est pas vraiment utile à moins que votre base de code soit suffisamment grande pour que l'approche simple devienne un problème. Mon intuition est que DI ne sera réellement une amélioration que lorsque votre application aura plus de 20 000 lignes de code et / ou plus de 20 dépendances sur d'autres bibliothèques ou frameworks. Si votre application est plus petite que cela, vous pouvez toujours préférer programmer dans un style DI, mais la différence ne sera pas aussi dramatique.
Daniel Pryden
2
@DanielPryden je ne pense pas que la taille du code soit aussi importante que la dynamique de votre code. si vous ajoutez régulièrement de nouveaux modules qui correspondent à la même interface, vous n'aurez pas à changer le code dépendant aussi souvent.
FistOfFury
35

Comme les autres réponses l'ont indiqué, l'injection de dépendances est un moyen de créer vos dépendances en dehors de la classe qui l'utilise. Vous les injectez de l'extérieur et prenez le contrôle de leur création loin de l'intérieur de votre classe. C'est aussi pourquoi l'injection de dépendances est une réalisation du principe d'inversion de contrôle (IoC).

IoC est le principe, où DI est le modèle. La raison pour laquelle vous pourriez "avoir besoin de plus d'un enregistreur" n'est jamais réellement rencontrée, d'après mon expérience, mais la vraie raison est que vous en avez vraiment besoin, chaque fois que vous testez quelque chose. Un exemple:

Ma fonction:

Lorsque je regarde une offre, je veux signaler que je l'ai regardée automatiquement, pour ne pas oublier de le faire.

Vous pouvez tester ceci comme ceci:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var formdata = { . . . }

    // System under Test
    var weasel = new OfferWeasel();

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(new DateTime(2013,01,13,13,01,0,0));
}

Donc quelque part dans le OfferWeasel, il vous construit un objet d'offre comme celui-ci:

public class OfferWeasel
{
    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = DateTime.Now;
        return offer;
    }
}

Le problème ici est que ce test échouera très probablement toujours, car la date qui est définie sera différente de la date affirmée, même si vous venez de mettre DateTime.Nowle code de test, il pourrait être désactivé par quelques millisecondes et sera donc échouent toujours. Une meilleure solution serait maintenant de créer une interface pour cela, qui vous permet de contrôler l'heure qui sera définie:

public interface IGotTheTime
{
    DateTime Now {get;}
}

public class CannedTime : IGotTheTime
{
    public DateTime Now {get; set;}
}

public class ActualTime : IGotTheTime
{
    public DateTime Now {get { return DateTime.Now; }}
}

public class OfferWeasel
{
    private readonly IGotTheTime _time;

    public OfferWeasel(IGotTheTime time)
    {
        _time = time;
    }

    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = _time.Now;
        return offer;
    }
}

L'interface est l'abstraction. L'un est la vraie chose, et l'autre vous permet de simuler un certain temps là où cela est nécessaire. Le test peut ensuite être modifié comme suit:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var date = new DateTime(2013, 01, 13, 13, 01, 0, 0);
    var formdata = { . . . }

    var time = new CannedTime { Now = date };

    // System under test
    var weasel= new OfferWeasel(time);

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(date);
}

Comme ça, vous avez appliqué le principe de "l'inversion de contrôle", en injectant une dépendance (obtention de l'heure courante). La principale raison pour cela est de faciliter les tests unitaires isolés, il existe d'autres façons de le faire. Par exemple, une interface et une classe ici ne sont pas nécessaires car en C #, les fonctions peuvent être transmises en tant que variables, donc au lieu d'une interface, vous pouvez utiliser a Func<DateTime>pour obtenir la même chose. Ou, si vous adoptez une approche dynamique, vous passez juste n'importe quel objet qui a la méthode équivalente ( typage du canard ), et vous n'avez pas du tout besoin d'une interface.

Vous n'aurez presque jamais besoin de plus d'un enregistreur. Néanmoins, l'injection de dépendances est essentielle pour le code typé statiquement tel que Java ou C #.

Et ... Il convient également de noter qu'un objet ne peut remplir correctement son objectif au moment de l'exécution, si toutes ses dépendances sont disponibles, donc il n'y a pas beaucoup d'utilité dans la configuration de l'injection de propriété. À mon avis, toutes les dépendances doivent être satisfaites lorsque le constructeur est appelé, donc l'injection de constructeur est la chose à faire.

J'espère que cela a aidé.

cesseur
la source
4
Cela ressemble en fait à une terrible solution. J'écrirais certainement du code plus comme Daniel Pryden, mais pour ce test unitaire spécifique, je ferais juste DateTime.Maintenant avant et après la fonction et vérifier si le temps est entre les deux? Ajouter plus d'interfaces / beaucoup plus de lignes de code me semble une mauvaise idée.
3
Je n'aime pas les exemples génériques A (B) et je n'ai jamais pensé qu'un enregistreur soit requis pour avoir 100 implémentations. Ceci est un exemple que j'ai récemment rencontré et c'est l'une des 5 façons de le résoudre, où l'on incluait réellement l'utilisation de PostSharp. Il illustre une approche d'injection de ctor basée sur une classe classique. Pourriez-vous fournir un meilleur exemple du monde réel où vous avez rencontré une bonne utilisation de DI?
cessor
2
Je n'ai jamais vu une bonne utilisation de DI. C'est pourquoi j'ai écrit la question.
2
Je ne l'ai pas trouvé utile. Mon code est toujours facile à faire. Il semble que DI soit bon pour les grandes bases de code avec un mauvais code.
1
Sachez que même dans les petits programmes fonctionnels, chaque fois que vous avez f (x), g (f), vous utilisez déjà l'injection de dépendance, donc chaque continuation dans JS compterait comme une injection de dépendance. Je suppose que vous l'utilisez déjà;)
cessor
15

Je pense que la réponse classique est de créer une application plus découplée, qui ne sait pas quelle implémentation sera utilisée pendant l'exécution.

Par exemple, nous sommes un fournisseur de paiement central, travaillant avec de nombreux fournisseurs de paiement dans le monde. Cependant, lorsqu'une demande est faite, je n'ai aucune idée du processeur de paiement que je vais appeler. Je pourrais programmer une classe avec une tonne de boîtiers de commutation, tels que:

class PaymentProcessor{

    private String type;

    public PaymentProcessor(String type){
        this.type = type;
    }

    public void authorize(){
        if (type.equals(Consts.PAYPAL)){
            // Do this;
        }
        else if(type.equals(Consts.OTHER_PROCESSOR)){
            // Do that;
        }
    }
}

Imaginez maintenant que vous devrez maintenant conserver tout ce code dans une seule classe car il n'est pas découplé correctement, vous pouvez imaginer que pour chaque nouveau processeur que vous prendrez en charge, vous devrez créer un nouveau cas if // switch pour chaque méthode, cela ne fait que se compliquer, cependant, en utilisant l'injection de dépendance (ou l'inversion de contrôle - comme on l'appelle parfois, ce qui signifie que celui qui contrôle l'exécution du programme n'est connu qu'au moment de l'exécution, et non pas de complication), vous pourriez réaliser quelque chose très soigné et maintenable.

class PaypalProcessor implements PaymentProcessor{

    public void authorize(){
        // Do PayPal authorization
    }
}

class OtherProcessor implements PaymentProcessor{

    public void authorize(){
        // Do other processor authorization
    }
}

class PaymentFactory{

    public static PaymentProcessor create(String type){

        switch(type){
            case Consts.PAYPAL;
                return new PaypalProcessor();

            case Consts.OTHER_PROCESSOR;
                return new OtherProcessor();
        }
    }
}

interface PaymentProcessor{
    void authorize();
}

** Le code ne se compile pas, je sais :)

Itai Sagi
la source
+1, car il semble que vous disiez en avoir besoin là où vous utilisez des méthodes / interfaces virtuelles. Mais c'est encore rare. Je le transmettrais toujours comme un new ThatProcessor()plutôt que d'utiliser un cadre
@ItaiS Vous pouvez éviter les innombrables commutateurs avec le modèle de conception d'usine de classe. Utilisez la réflexion System.Reflection.Assembly.GetExecutingAssembly (). CreateInstance ()
domenicr
@domenicr ofcourse! mais je voulais l'expliquer dans un exemple simplifié
Itai Sagi
Je suis d'accord avec l'explication ci-dessus, sauf la nécessité d'une classe d'usine. Au moment où nous implémentons la classe d'usine, c'est simplement une sélection brute. La meilleure explication ci-dessus que j'ai trouvée dans le chapitre du Poymorphisme et de la fonction virtuelle par Bruce Erkel. La véritable DI doit être libre de la sélection et le type d'objet doit être décidé automatiquement au moment de l'exécution via l'interface. C'est aussi ce qu'est le véritable comportement polymorphe.
Arvind Krmar
Par exemple (selon c ++), nous avons une interface commune qui prend juste la référence à la classe de base et implémente le comportement de sa classe dérivée sans sélection. void tune (Instrument & i) {i.play (middleC); } int main () {Flûte à vent; air (flûte); } L'instrument est la classe de base, le vent en dérive. Selon c ++, la fonction virtuelle permet d'implémenter le comportement de la classe dérivée via une interface commune.
Arvind Krmar
6

La principale raison d'utiliser DI est que vous voulez mettre la responsabilité de la connaissance de l'implémentation là où la connaissance est là. L'idée de DI est très en ligne avec l'encapsulation et la conception par interface. Si le serveur frontal demande au serveur principal certaines données, il est sans importance pour le serveur principal comment le serveur principal résout cette question. Cela dépend du gestionnaire de demandes.

Cela est déjà courant dans la POO depuis longtemps. Plusieurs fois, la création de morceaux de code comme:

I_Dosomething x = new Impl_Dosomething();

L'inconvénient est que la classe d'implémentation est toujours codée en dur, d'où la connaissance initiale de l'implémentation utilisée. DI pousse la conception par interface un peu plus loin, que la seule chose que le frontal doit savoir est la connaissance de l'interface. Entre le DYI et le DI se trouve le modèle d'un localisateur de service, car le frontal doit fournir une clé (présente dans le registre du localisateur de service) pour permettre à sa demande d'être résolue. Exemple de localisateur de service:

I_Dosomething x = ServiceLocator.returnDoing(String pKey);

Exemple DI:

I_Dosomething x = DIContainer.returnThat();

L'une des exigences de DI est que le conteneur doit pouvoir savoir quelle classe est l'implémentation de quelle interface. Par conséquent, un conteneur DI nécessite une conception fortement typée et une seule implémentation pour chaque interface à la fois. Si vous avez besoin de plus d'implémentations d'une interface en même temps (comme une calculatrice), vous avez besoin du localisateur de service ou du modèle de conception d'usine.

D (b) I: Injection de dépendance et conception par interface. Cette restriction n'est cependant pas un très gros problème pratique. L'avantage d'utiliser D (b) I est qu'il sert la communication entre le client et le fournisseur. Une interface est une perspective sur un objet ou un ensemble de comportements. Cette dernière est cruciale ici.

Je préfère l'administration des contrats de service avec D (b) I dans le codage. Ils devraient aller ensemble. L'utilisation de D (b) I comme solution technique sans gestion organisationnelle des contrats de service n'est pas très bénéfique à mon point de vue, car DI n'est alors qu'une couche supplémentaire d'encapsulation. Mais lorsque vous pouvez l'utiliser avec l'administration organisationnelle, vous pouvez vraiment utiliser le principe d'organisation D (b) I proposé. Il peut vous aider à long terme à structurer la communication avec le client et d'autres services techniques sur des sujets tels que les tests, le versioning et le développement d'alternatives. Lorsque vous avez une interface implicite comme dans une classe codée en dur, elle est beaucoup moins communicable dans le temps que lorsque vous la rendez explicite à l'aide de D (b) I. Tout se résume à la maintenance, qui se fait au fil du temps et non à la fois. :-)

Loek Bergman
la source
1
"L'inconvénient est que la classe d'implémentation est toujours codée en dur" <- la plupart du temps, il n'y a qu'une seule implémentation et comme je l'ai dit, je ne peux pas penser à du code sans nom qui nécessite une interface qui n'est pas déjà intégrée (.NET ).
@ acidzombie24 Peut-être ... mais comparez l'effort d'implémentation d'une solution utilisant DI depuis le début à l'effort de changer une solution non DI plus tard si vous avez besoin d'interfaces. J'irais presque toujours avec la première option. Il vaut mieux payer maintenant 100 $ au lieu d'avoir à payer 100 000 $ demain.
Golo Roden
1
@GoloRoden En effet, la maintenance est le problème clé en utilisant des techniques comme D (b) I. Cela représente 80% des coûts d'une demande. Une conception dans laquelle le comportement requis est rendu explicite à l'aide d'interfaces dès le départ permet à l'organisation d'économiser beaucoup de temps et d'argent par la suite.
Loek Bergman
Je ne vais pas vraiment comprendre jusqu'à ce que je doive le payer, car jusqu'à présent, j'ai payé 0 $ et jusqu'à présent, je n'ai encore qu'à payer 0 $. Mais je paie 0,05 $ pour garder chaque ligne ou fonction propre.