Pourquoi ai-je besoin d'un conteneur IoC par opposition à un code DI simple? [fermé]

598

J'utilise Dependency Injection (DI) depuis un certain temps, injectant soit dans un constructeur, une propriété ou une méthode. Je n'ai jamais ressenti le besoin d'utiliser un conteneur d'inversion de contrôle (IoC). Cependant, plus je lis, plus je ressens de pression de la part de la communauté pour utiliser un conteneur IoC.

J'ai joué avec des conteneurs .NET comme StructureMap , NInject , Unity et Funq . Je n'arrive toujours pas à voir comment un conteneur IoC va bénéficier / améliorer mon code.

J'ai aussi peur de commencer à utiliser un conteneur au travail parce que beaucoup de mes collègues verront du code qu'ils ne comprennent pas. Beaucoup d'entre eux peuvent être réticents à apprendre de nouvelles technologies.

Veuillez me convaincre que je dois utiliser un conteneur IoC. Je vais utiliser ces arguments lorsque je parlerai à mes collègues développeurs au travail.

Vadim
la source
6
Bonne question. Je réponds à cela dans les commentaires car je suis très nouveau dans l'idée du CIO. Cela ressemble à l'idée de composants plug and play et de couplage desserré. Que vous ayez besoin d'utiliser un autre composant à la place du composant actuel, cela dépend des besoins. L'utilisation d'IOC est de préparer le code pour un tel changement, si cela se produit IMO.
shahkalpesh
3
@shahkalpesh mais je peux réaliser un couplage lâche avec une DI simple. Cependant, je vois votre point au cas où j'utilise un fichier de configuration. Cependant, je ne sais pas si j'aime utiliser le fichier de configuration. Les fichiers de configuration sont très verbeux, des difficultés de refactorisation et de basculement entre plusieurs fichiers.
Vadim
110
juste ajouter un commentaire car il semble que vous cherchiez des raisons d'utiliser principalement l'IoC; mais quelque chose d'autre doit être dit à propos de votre problème. Vous ne pouvez pas écrire votre code au niveau de la pire personne de votre équipe ou de peur qu'elle ne veuille pas apprendre. Vous avez la responsabilité non seulement d'être un professionnel, mais aussi de votre avenir à grandir et votre équipe ne devrait pas vous retenir.
Kevin Sheffield
4
@Kevin, En fait, nous utilisons l'IoC. Ce n'était pas aussi difficile que prévu d'enseigner à tout le monde l'IoC.
Vadim
21
Je n'ai aucune idée pourquoi les modérateurs ferment des questions massivement votées et utiles. Peut-être que dans ce cas, Will ne comprend pas la question, ou il ne comprend pas pourquoi les gens utilisent stackexchange? Si ce n'est "pas un bon ajustement pour le format Q&A de stackexchange", alors considérez peut-être que votre politique est erronée et pas toutes ces centaines de questions utiles avec des centaines de votes positifs et de réponses utiles votées.
NickG

Réponses:

441

Wow, je ne peux pas croire que Joel favoriserait ceci:

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

sur ceci:

var svc = IoC.Resolve<IShippingService>();

Beaucoup de gens ne réalisent pas que votre chaîne de dépendances peut devenir imbriquée, et il devient rapidement difficile de les câbler manuellement. Même avec des usines, la duplication de votre code n'en vaut pas la peine.

Les conteneurs IoC peuvent être complexes, oui. Mais pour ce cas simple, j'ai montré que c'est incroyablement facile.


D'accord, justifions cela encore plus. Supposons que vous ayez des entités ou des objets de modèle que vous souhaitez lier à une interface utilisateur intelligente. Cette interface utilisateur intelligente (nous l'appellerons Shindows Morms) souhaite que vous implémentiez INotifyPropertyChanged afin qu'il puisse effectuer le suivi des modifications et mettre à jour l'interface utilisateur en conséquence.

"OK, ça ne semble pas si difficile", alors vous commencez à écrire.

Vous commencez par ceci:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CustomerSince { get; set; }
    public string Status { get; set; }
}

..et finir avec ceci :

public class UglyCustomer : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            string oldValue = _firstName;
            _firstName = value;
            if(oldValue != value)
                OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            string oldValue = _lastName;
            _lastName = value;
            if(oldValue != value)
                OnPropertyChanged("LastName");
        }
    }

    private DateTime _customerSince;
    public DateTime CustomerSince
    {
        get { return _customerSince; }
        set
        {
            DateTime oldValue = _customerSince;
            _customerSince = value;
            if(oldValue != value)
                OnPropertyChanged("CustomerSince");
        }
    }

    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            string oldValue = _status;
            _status = value;
            if(oldValue != value)
                OnPropertyChanged("Status");
        }
    }

    protected virtual void OnPropertyChanged(string property)
    {
        var propertyChanged = PropertyChanged;

        if(propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

C'est du code de plomberie dégoûtant, et je maintiens que si vous écrivez du code comme ça à la main, vous volez votre client . Il existe une façon de travailler meilleure et plus intelligente.

Jamais entendu ce terme, travailler plus intelligemment, pas plus dur?

Eh bien, imaginez qu'un gars intelligent de votre équipe est venu et a dit: "Voici un moyen plus simple"

Si vous rendez vos propriétés virtuelles (calmez-vous, ce n'est pas si grave), nous pouvons automatiquement intégrer ce comportement de propriété. (Cela s'appelle AOP, mais ne vous inquiétez pas pour le nom, concentrez-vous sur ce qu'il va faire pour vous)

Selon l'outil IoC que vous utilisez, vous pouvez faire quelque chose qui ressemble à ceci:

var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());

Pouf! Tout ce manuel INotifyPropertyChanged BS est maintenant généré automatiquement pour vous, sur chaque configurateur de propriété virtuelle de l'objet en question.

Est-ce magique? OUI ! Si vous pouvez faire confiance au fait que ce code fait son travail, vous pouvez ignorer en toute sécurité toute cette propriété enveloppant mumbo-jumbo. Vous avez des problèmes commerciaux à résoudre.

Quelques autres utilisations intéressantes d'un outil IoC pour faire de l'AOP:

  • Transactions de bases de données déclaratives et imbriquées
  • Unité de travail déclarative et imbriquée
  • Enregistrement
  • Conditions de pré / post (conception par contrat)
Ben Scheirman
la source
28
Oups, vous avez oublié de rendre toutes les propriétés virtuelles et cela n'a pas fonctionné. Est-ce le temps qu'il vous fallait pour déboguer ce vol de votre client aussi? (Toutes mes excuses si vous n'utilisez pas DynamicProxy.: P)
Thom
17
Vous sacrifiez BEAUCOUP de clarté en utilisant le wrapper INotifyPropertyChanged. Si cela vous prend plus de quelques minutes pour écrire cette plomberie, vous êtes dans la mauvaise entreprise. Moins n'est pas plus quand il s'agit de la verbosité de votre code!
dépassé
39
@overstood - Je suis complètement en désaccord. D'abord, où la clarté importe-t-elle dans cet exemple? Le plus proche du consommateur. Le consommateur demande explicitement INotifyPropertyChanged. Ce n'est pas parce que nous pouvons créer ce type de code avec une génération de code micro ou macro que c'est une bonne idée de l'écrire. Ce gunk INotifyPropertyChanged n'est que par cœur, banal, plomberie et nuit en fait à la lisibilité de la classe en question.
Ben Scheirman
31
Marqué parce que vous confondez le modèle d'injection de dépendance et le modèle de localisateur de service. Le premier est destiné à être utilisé au lieu du second. Par exemple, un objet (ou bien l'ensemble du système) ne doit jamais appeler Resolve (...) pour obtenir une instance de IShippingService. Les objets qui ont besoin d'un IShippingService doivent recevoir une référence à l'instance qu'ils doivent utiliser lors de leur construction, et une couche supérieure est chargée de connecter les deux objets ensemble.
Nat
30
Quoi que ce soit (IoC, DynamicProxy, AOP ou magie noire ), quelqu'un peut-il me lier à un article concret / une documentation spécifique dans un cadre qui fournit plus de détails pour y parvenir?
fostandy
138

Je suis avec toi, Vadim. Les conteneurs IoC prennent un concept simple, élégant et utile, et en font quelque chose que vous devez étudier pendant deux jours avec un manuel de 200 pages.

Personnellement, je suis perplexe sur la façon dont la communauté IoC a pris un bel article élégant de Martin Fowler et l'a transformé en un tas de cadres complexes, généralement avec des manuels de 200 à 300 pages.

J'essaie de ne pas porter de jugement (HAHA!), Mais je pense que les personnes qui utilisent des conteneurs IoC sont (A) très intelligentes et (B) manquent d'empathie pour les personnes qui ne sont pas aussi intelligentes qu'eux. Tout est parfaitement logique pour eux, ils ont donc du mal à comprendre que de nombreux programmeurs ordinaires trouveront les concepts déroutants. C'est la malédiction du savoir . Les gens qui comprennent les conteneurs IoC ont du mal à croire qu'il y a des gens qui ne le comprennent pas.

L'avantage le plus précieux de l'utilisation d'un conteneur IoC est que vous pouvez avoir un commutateur de configuration en un seul endroit qui vous permet de basculer entre, disons, le mode test et le mode production. Par exemple, supposons que vous ayez deux versions de vos classes d'accès à la base de données ... une version qui s'est connectée de manière agressive et a fait beaucoup de validation, que vous avez utilisée pendant le développement, et une autre version sans journalisation ou validation qui était incroyablement rapide pour la production. C'est agréable de pouvoir basculer entre eux en un seul endroit. D'un autre côté, il s'agit d'un problème assez trivial, facile à gérer de manière plus simple sans la complexité des conteneurs IoC.

Je crois que si vous utilisez des conteneurs IoC, votre code devient, franchement, beaucoup plus difficile à lire. Le nombre d'endroits que vous devez regarder pour comprendre ce que le code essaie de faire augmente d'au moins un. Et quelque part dans le ciel, un ange crie.

Joel Spolsky
la source
81
Je pense que vous avez un peu mélangé vos faits Joel. L'IoC et les conteneurs sont venus en premier, puis le traité de Martin, et non l'inverse.
Glenn Block
55
La plupart des conteneurs vous permettent de démarrer très rapidement. Avec autofac, carte de structure, unité, ninject, etc., vous devriez pouvoir faire fonctionner le conteneur en 5 minutes environ. Oui, ils ont des fonctionnalités avancées, mais vous n'en avez pas besoin pour décoller.
Glenn Block
58
Joel, je pensais comme toi. Ensuite, j'ai littéralement et sérieusement passé cinq minutes à comprendre comment faire fonctionner la configuration la plus basique et j'ai été étonné de la règle 80/20 en action. Cela rend le code beaucoup plus propre, surtout dans les cas simples.
Justin Bozonier
55
Je programme depuis moins de deux ans et j'ai lu le wiki Ninject et je l'ai compris. J'utilise DI dans de nombreux endroits depuis lors. As-tu vu ça? Moins de deux ans et lecture de quelques pages sur un wiki. Je ne suis ni sorcier ni rien. Je ne me considère pas comme un génie, mais c'était facile pour moi de comprendre. Je ne peux pas imaginer que quelqu'un qui comprend vraiment OO ne puisse pas le saisir. De plus, de quels manuels de 200 à 300 pages parlez-vous? Je n'en ai jamais vu. Tous les conteneurs IoC que j'ai utilisés sont assez courts et précis.
JR Garcia
35
+1 bien écrit et réfléchi. Une chose que je pense que beaucoup de gens ne réalisent pas, c'est que l'ajout dans un cadre ou autre pour gérer "par magie" quelque chose ajoute automatiquement de la complexité et des limites à un système. Parfois, cela en vaut la peine, mais souvent quelque chose est ignoré et l'entropie est publiée dans le code en essayant de résoudre un problème imposé par des limitations. Cela peut faire gagner du temps à l'avance, mais les gens doivent comprendre que cela peut coûter 100 fois plus tard en essayant de résoudre un problème obscur que seule une poignée de personnes ont vraiment suffisamment de connaissances pour résoudre.
kemiller2002
37

Vraisemblablement, personne ne vous oblige à utiliser un cadre de conteneur DI. Vous utilisez déjà DI pour découpler vos classes et améliorer la testabilité, vous bénéficiez donc de nombreux avantages. Bref, vous privilégiez la simplicité, ce qui est généralement une bonne chose.

Si votre système atteint un niveau de complexité où la DI manuelle devient une corvée (c'est-à-dire augmente la maintenance), pesez cela par rapport à la courbe d'apprentissage d'équipe d'un cadre de conteneur DI.

Si vous avez besoin de plus de contrôle sur la gestion de la durée de vie des dépendances (c'est-à-dire si vous ressentez le besoin d'implémenter le modèle Singleton), regardez les conteneurs DI.

Si vous utilisez un conteneur DI, utilisez uniquement les fonctionnalités dont vous avez besoin. Ignorez le fichier de configuration XML et configurez-le en code si cela est suffisant. Tenez-vous à l'injection du constructeur. Les bases de Unity ou StructureMap peuvent être condensées en quelques pages.

Il y a un excellent article de blog de Mark Seemann à ce sujet: Quand utiliser un conteneur DI

TrueWill
la source
Très bonne réponse. La seule chose sur laquelle je différerais d'opinion est de savoir si l'injection manuelle peut être considérée comme moins complexe ou non comme une corvée.
wekempf
+1. L'une des meilleures réponses ici. Merci aussi pour le lien de l'article de blog.
stakx - ne contribue plus
1
+1 pour le point de vue équilibré. Les conteneurs IoC ne sont pas toujours bons, et ils ne sont pas toujours mauvais. Si vous ne voyez pas de besoin, ne l'utilisez pas. Sachez simplement que l'outil est là si vous voyez un besoin.
Phil
32

À mon avis, l'avantage numéro un d'un IoC est la possibilité de centraliser la configuration de vos dépendances.

Si vous utilisez actuellement l'injection de dépendances, votre code pourrait ressembler à ceci

public class CustomerPresenter
{
  public CustomerPresenter() : this(new CustomerView(), new CustomerService())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Si vous avez utilisé une classe IoC statique, contrairement aux fichiers de configuration, à mon humble avis, les plus confus, vous pourriez avoir quelque chose comme ceci:

public class CustomerPresenter
{
  public CustomerPresenter() : this(IoC.Resolve<ICustomerView>(), IoC.Resolve<ICustomerService>())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Ensuite, votre classe Static IoC ressemblerait à ceci, j'utilise Unity ici.

public static IoC
{
   private static readonly IUnityContainer _container;
   static IoC()
   {
     InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerView, CustomerView>();
      _container.RegisterType<ICustomerService, CustomerService>();
      // all other RegisterTypes and RegisterInstances can go here in one file.
      // one place to change dependencies is good.
   }
}
bendewey
la source
11
Ben, ce dont vous parlez, c'est vraiment de l'emplacement du service, pas de l'injection de dépendance - il y a une différence. Je préfère toujours le premier au dernier. stevenharman.net/blog/archive/2009/09/25/…
stevenharman
23
Au lieu de faire IoC.Resolve <T> dans votre constructeur par défaut, vous devez tirer parti de la capacité du conteneur IOC à construire l'objet pour vous. Débarrassez-vous de ce constructeur vide et laissez le conteneur IOC construire l'objet pour vous. var presenter = IoC.Resolve <CustomerPresenter> (); le tour est joué! toutes les dépendances sont déjà câblées.
Ben Scheirman
12
L'inconvénient de la centralisation de ce type de configuration est la décontextualisation des connaissances. Joel souligne ce problème en disant: "le code devient, franchement, beaucoup plus difficile à lire".
Scott Bellware
6
@sbellware, quelles connaissances? L'interface étant considérée comme une dépendance primaire sert de contrat et devrait être suffisante pour transmettre à la fois son but et son comportement, non? Je suppose que vous faites peut-être référence aux connaissances acquises grâce à la spéléologie dans la mise en œuvre du contrat, auquel cas vous auriez besoin de retrouver la configuration appropriée? Je compte sur un ordinateur (R # dans VS, IntelliJ, etc.) pour cela car ils sont parfaits pour naviguer dans les nœuds et les bords d'un graphique; Je réserve ma pile de cerveaux au contexte du chantier sur lequel je me concentre actuellement.
stevenharman
7
Steve, je connais le texte .NET et j'ai parcouru cette marche pendant de nombreuses années. Je connais aussi intimement d'autres modes et formes, et je comprends visuellement qu'il y a de vrais compromis. Je sais comment et quand faire ces compromis, mais la diversité de ma vie professionnelle me permet au moins de comprendre concrètement le point de vue de Joel. Plutôt que de me faire la leçon sur les outils et les astuces que j'utilise depuis plus longtemps que la plupart des monoculturels .NET, dites-moi quelque chose que je ne sais pas. Je réserve la pile de mon cerveau à une pensée critique diversifiée plutôt qu'à une stase et une orthodoxie alt.net.
Scott Bellware
32

Les conteneurs IoC sont également utiles pour charger des dépendances de classe profondément imbriquées. Par exemple, si vous disposiez du code suivant à l'aide de l'injection de dépendances.

public void GetPresenter()
{
    var presenter = new CustomerPresenter(new CustomerService(new CustomerRepository(new DB())));
}

class CustomerPresenter
{
    private readonly ICustomerService service;
    public CustomerPresenter(ICustomerService service)
    {
        this.service = service;
    }
}

class CustomerService
{
    private readonly IRespository<Customer> repository;
    public CustomerService(IRespository<Customer> repository)
    {
        this.repository = repository;
    }
}

class CustomerRepository : IRespository<Customer>
{
    private readonly DB db;
    public CustomerRepository(DB db)
    {
        this.db = db;
    }
}

class DB { }

Si vous aviez toutes ces dépendances chargées dans le conteneur IoC, vous pourriez résoudre le service client et toutes les dépendances enfants seront automatiquement résolues.

Par exemple:

public static IoC
{
   private IUnityContainer _container;
   static IoC()
   {
       InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerService, CustomerService>();
      _container.RegisterType<IRepository<Customer>, CustomerRepository>();
   }

   static T Resolve<T>()
   {
      return _container.Resolve<T>();
   }
}

public void GetPresenter()
{
   var presenter = IoC.Resolve<CustomerPresenter>();
   // presenter is loaded and all of its nested child dependencies 
   // are automatically injected
   // -
   // Also, note that only the Interfaces need to be registered
   // the concrete types like DB and CustomerPresenter will automatically 
   // resolve.
}
bendewey
la source
Mais la classe IoC est appelée à l'aide d'un anti-modèle de localisateur de service. Doit_container être passé en utilisant le constructeur DI au lieu d'appeler statique T Resolve <T> ()?
Michael Freidgeim
@bendewey Votre exemple implique qu'il transmet automatiquement les arguments aux constructeurs pour le nouveau CustomerService et le nouveau CustomerRepository. Est-ce ainsi que cela fonctionne? Et si vous avez également d'autres paramètres supplémentaires?
Brain2000
28

Je suis un fan de la programmation déclarative (regardez le nombre de questions SQL auxquelles je réponds), mais les conteneurs IoC que j'ai examinés semblent trop mystérieux pour leur propre bien.

... ou peut-être que les développeurs de conteneurs IoC sont incapables d'écrire une documentation claire.

... ou bien les deux sont vrais à un degré ou à un autre.

Je ne pense pas que le concept d'un conteneur IoC soit mauvais. Mais l'implémentation doit être à la fois suffisamment puissante (c'est-à-dire flexible) pour être utile dans une grande variété d'applications, mais simple et facile à comprendre.

C'est probablement six d'un et une demi-douzaine de l'autre. Une application réelle (pas un jouet ou une démo) est forcément complexe, représentant de nombreux cas d'angle et des exceptions aux règles. Soit vous encapsulez cette complexité en code impératif, soit en code déclaratif. Mais vous devez le représenter quelque part.

Bill Karwin
la source
5
J'aime votre observation sur la présence inévitable de la complexité. La différence entre le câblage de dépendances basé sur un conteneur et manuel est que l'utilisation d'un conteneur, vous pouvez vous concentrer sur chaque composant individuellement, et ainsi gérer la complexité croissante d'une manière assez linéaire. Les systèmes avec câblage de dépendance manuel sont plus difficiles à faire évoluer, car le fait de configurer un composant est difficile à isoler de la configuration de tout le reste.
Nicholas Blumhardt
J'aime AspectJ. Ce n'est pas Java, mais ce n'est pas XML non plus. Ce genre de IS Java dans la mesure où il se sent et ressemble à Java en étant une extension de celui - ci. Je préfère garder mes aspects hors de mes POJO et dans ma logique métier. J'aimerais presque aussi garder mes IoC à l'écart. Il y a trop de XML dans le câblage des outils, à mon humble avis. Si je dois avoir des fichiers de configuration, laissez-les être des scripts BeanShell. / Java, appliquez vos workalikes .Net ici ->.
Chris K
2
Je comprends certainement votre réponse, mais je pense qu'une grande partie du problème est que beaucoup de cadres IoC (et, vraiment, beaucoup d'autres cadres pour d'autres choses aussi) sont décrits et documentés pour d'autres déjà très familiers avec le concepts et terminologie requis. J'apprends beaucoup de cela après avoir hérité du code d'un autre programmeur. J'ai du mal à comprendre pourquoi le code utilise l'IoC alors que les «graphiques d'objet» sont assez petits et qu'il n'y a pas de véritable couverture de test du code. En tant que programmeur SQL, vous pouvez probablement mieux apprécier l'utilisation de l'IoC avec un ORM.
Kenny Evitt
1
@KennyEvitt, bon point, il semblerait prématuré d'utiliser un framework IoC si l'on a une application simple et que l'on n'a même pas pris la peine d'avoir une bonne couverture de test.
Bill Karwin
27

L'utilisation d'un conteneur consiste principalement à passer d'un style d'initialisation et de configuration impératif / scripté à un style déclaratif . Cela peut avoir quelques effets bénéfiques différents:

  • La réduction hairball des routines-programme principal.
  • Activation de capacités de reconfiguration en temps de déploiement assez approfondies.
  • Faire du style injectable de dépendance le chemin de la moindre résistance pour les nouveaux travaux.

Bien sûr, il peut y avoir des difficultés:

  • Le code qui nécessite une gestion complexe de démarrage / arrêt / cycle de vie peut ne pas être facilement adapté à un conteneur.
  • Vous devrez probablement gérer tous les problèmes de culture personnelle, de processus et d'équipe - mais c'est pourquoi vous avez demandé ...
  • Certaines boîtes à outils deviennent rapidement lourdes elles-mêmes, encourageant le type de dépendance profonde contre laquelle de nombreux conteneurs DI ont commencé comme un contre-coup.
Jeffrey Hantin
la source
23

Il me semble que vous avez déjà construit votre propre conteneur IoC (en utilisant les différents modèles décrits par Martin Fowler) et vous demandez pourquoi l'implémentation de quelqu'un d'autre est meilleure que la vôtre.

Donc, vous avez un tas de code qui fonctionne déjà. Et vous vous demandez pourquoi vous voudriez le remplacer par l'implémentation de quelqu'un d'autre.

Avantages pour envisager un conteneur IoC tiers

  • Vous obtenez des bugs corrigés gratuitement
  • La conception de la bibliothèque peut être meilleure que la vôtre
  • Les gens connaissent peut-être déjà la bibliothèque en question
  • La bibliothèque peut être plus rapide que la vôtre
  • Il peut avoir certaines fonctionnalités que vous souhaiteriez implémenter mais que vous n'avez jamais eu le temps (avez-vous un localisateur de service?)

Les inconvénients

  • Vous obtenez des bugs introduits gratuitement :)
  • La conception de la bibliothèque peut être pire que la vôtre
  • Vous devez apprendre une nouvelle API
  • Trop de fonctionnalités que vous n'utiliserez jamais
  • Il est généralement plus difficile de déboguer du code que vous n'avez pas écrit
  • La migration à partir d'un conteneur IoC précédent peut être fastidieuse

Alors, pesez vos avantages contre vos inconvénients et prenez une décision.

Sam Saffron
la source
Je suis d'accord avec vous Sam - DI est un type d'IoC, donc si l'affiche originale fait DI, ils font déjà IoC alors la vraie question est de savoir pourquoi lancer le vôtre.
Jamie Love
3
Je ne suis pas d'accord. Le CIO est une façon de faire l'ID. Les conteneurs IOC font DI en prenant le contrôle de l'instanciation de type en utilisant la réflexion dynamique d'exécution pour satisfaire et injecter les dépendances. L'OP suggère qu'il fait une DI statique et réfléchit à la raison pour laquelle il doit échanger les avantages du temps de compilation en vérifiant les avantages souvent associés à la réflexion dynamique du CIO.
Maxm007
17

Je pense que la plupart de la valeur d'un IoC est obtenue en utilisant DI. Puisque vous le faites déjà, le reste de l'avantage est progressif.

La valeur que vous obtiendrez dépendra du type d'application sur laquelle vous travaillez:

  • Pour les locataires multiples, le conteneur IoC peut prendre en charge une partie du code d'infrastructure pour charger différentes ressources client. Lorsque vous avez besoin d'un composant spécifique au client, utilisez un sélecteur personnalisé pour gérer la logique et ne vous en faites pas à partir de votre code client. Vous pouvez certainement le créer vous-même, mais voici un exemple de la façon dont une IoC peut vous aider.

  • Avec de nombreux points d'extensibilité, l'IoC peut être utilisé pour charger des composants à partir de la configuration. C'est une chose courante à construire mais les outils sont fournis par le conteneur.

  • Si vous souhaitez utiliser AOP pour certaines préoccupations transversales, l' IoC fournit des crochets pour intercepter les appels de méthode. Cela se fait moins souvent ad-hoc sur des projets, mais l'IoC le facilite.

J'ai déjà écrit des fonctionnalités comme celle-ci, mais si j'ai besoin de ces fonctionnalités maintenant, je préfère utiliser un outil prédéfini et testé s'il correspond à mon architecture.

Comme mentionné par d'autres, vous pouvez également configurer de manière centralisée les classes que vous souhaitez utiliser. Bien que cela puisse être une bonne chose, cela a un coût de mauvaise direction et de complication. Les composants de base pour la plupart des applications ne sont pas beaucoup remplacés, de sorte que le compromis est un peu plus difficile à faire.

J'utilise un conteneur IoC et j'apprécie la fonctionnalité, mais je dois admettre que j'ai remarqué le compromis: mon code devient plus clair au niveau de la classe et moins clair au niveau de l'application (c'est-à-dire la visualisation du flux de contrôle).

Steven Lyons
la source
16

Je suis un dépendant du CIO. J'ai du mal à justifier l'utilisation d'IOC pour DI dans la plupart des cas de nos jours. Les conteneurs IOC sacrifient la vérification du temps de compilation et, en retour, vous offrent une configuration "facile", une gestion complexe de la durée de vie et la découverte à la volée des dépendances lors de l'exécution. Je trouve que la perte de vérification du temps de compilation et la magie / exceptions qui en résultent ne valent pas les cloches et les sifflets dans la grande majorité des cas. Dans les applications de grande entreprise, il peut être très difficile de suivre ce qui se passe.

Je n'achète pas l'argument de la centralisation car vous pouvez également centraliser très facilement la configuration statique en utilisant une fabrique abstraite pour votre application et en reportant religieusement la création d'objet à la fabrique abstraite, c'est-à-dire faire une DI appropriée.

Pourquoi ne pas faire une DI statique sans magie comme ceci:

interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }

class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }

interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }

class Root : IRoot
{
    public IMiddle Middle { get; set; }

    public Root(IMiddle middle)
    {
        Middle = middle;
    }

}

class Middle : IMiddle
{
    public ILeaf Leaf { get; set; }

    public Middle(ILeaf leaf)
    {
        Leaf = leaf;
    }
}

class Leaf : ILeaf
{
    IServiceA ServiceA { get; set; }
    IServiceB ServiceB { get; set; }

    public Leaf(IServiceA serviceA, IServiceB serviceB)
    {
        ServiceA = serviceA;
        ServiceB = serviceB;
    }
}


interface IApplicationFactory
{
    IRoot CreateRoot();
}

abstract class ApplicationAbstractFactory : IApplicationFactory
{
    protected abstract IServiceA ServiceA { get; }
    protected abstract IServiceB ServiceB { get; }

    protected IMiddle CreateMiddle()
    {
        return new Middle(CreateLeaf());
    }

    protected ILeaf CreateLeaf()
    {
        return new Leaf(ServiceA,ServiceB);
    }


    public IRoot CreateRoot()
    {
        return new Root(CreateMiddle());
    }
}

class ProductionApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new ServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new ServiceB(); }
    }
}

class FunctionalTestsApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new StubServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new StubServiceB(); }
    }
}


namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var factory = new ProductionApplication();
            var root = factory.CreateRoot();

        }
    }

    //[TestFixture]
    class FunctionalTests
    {
        //[Test]
        public void Test()
        {
            var factory = new FunctionalTestsApplication();
            var root = factory.CreateRoot();
        }
    }
}

Votre configuration de conteneur est votre implémentation d'usine abstraite, vos enregistrements sont des implémentations de membres abstraits. Si vous avez besoin d'une nouvelle dépendance singleton, ajoutez simplement une autre propriété abstraite à la fabrique abstraite. Si vous avez besoin d'une dépendance transitoire, ajoutez simplement une autre méthode et injectez-la en tant que Func <>.

Avantages:

  • Toute la configuration et la configuration de création d'objet sont centralisées.
  • La configuration est juste du code
  • La vérification du temps de compilation facilite la maintenance car vous ne pouvez pas oublier de mettre à jour vos enregistrements.
  • Pas de magie de réflexion à l'exécution

Je recommande aux sceptiques de faire un prochain projet de champ vert et de vous demander honnêtement à quel moment vous avez besoin du conteneur. Il est facile de prendre en compte un conteneur IOC plus tard, car vous remplacez simplement une implémentation d'usine par un module de configuration de conteneur IOC.

Maxm007
la source
Cela ne me semble pas être un argument contre l'IoC, mais un argument contre les frameworks IoC configurables . Vos usines ici ne se résument-elles pas à de simples IoC codées en dur?
Mr.Mindor
Je pense que lorsque l'affiche mentionnait l'IoC, un cadre de conteneur IoC était destiné. Merci pour la rédaction originale, cela m'a convaincu de me passer d'un cadre. Donc, s'il est configurable dans les tests et dans le code de production, quel est l'avantage d'un framework "configurable"?
huggie
D'après ma compréhension de la terminologie, Inversion Of Control signifie trouver des dépendances au moment de l'exécution. Alors oui, on pourrait dire que l'usine est essentiellement un conteneur codé en dur ou statique, mais ce n'est pas un conteneur IOC. Les types sont résolus au moment de la compilation. C'est un argument contre l'utilisation de frameworks configurables qui utilisent des recherches de dictionnaire pour résoudre les types lors de l'exécution.
Maxm007
14

Le plus grand avantage de l'utilisation de conteneurs IoC pour moi (personnellement, j'utilise Ninject) a été d'éliminer le passage des paramètres et d'autres types d'objets d'état global.

Je ne programme pas pour le Web, le mien est une application console et, à de nombreux endroits, profondément dans l'arborescence d'objets, j'ai besoin d'avoir accès aux paramètres ou aux métadonnées spécifiés par l'utilisateur qui sont créés sur une branche complètement distincte de l'arborescence d'objets. Avec IoC, je dis simplement à Ninject de traiter les paramètres comme un singleton (car il n'y en a toujours qu'une seule), demande les paramètres ou le dictionnaire dans le constructeur et hop ... ils apparaissent comme par magie quand j'en ai besoin!

Sans utiliser un conteneur IoC, je devrais transmettre les paramètres et / ou les métadonnées à travers 2, 3, ..., n objets avant qu'il ne soit réellement utilisé par l'objet qui en avait besoin.

Les conteneurs DI / IoC présentent de nombreux autres avantages, comme d'autres personnes l'ont expliqué ici.Le passage de l'idée de créer des objets à la demande d'objets peut être époustouflant, mais l'utilisation de DI a été très utile pour moi et mon équipe, alors vous pouvez peut-être l'ajouter à votre arsenal!

Jeffrey Cameron
la source
2
C'est un énorme avantage. Je ne peux pas croire que personne d'autre ne l'ait mentionné auparavant. Sans la nécessité de passer ou de stocker des objets temporaires, le code est beaucoup plus propre.
Finglas
1
Les objets globaux @qble fonctionnent très bien ... si vous voulez que votre base de code soit un cauchemar étroitement couplé et non testable qui devient de plus en plus difficile à modifier au fil du temps. bonne chance avec ça.
Jeffrey Cameron
2
@JeffreyCameron IoC Container est un objet global. Il ne supprime pas vos dépendances globales.
qble
3
Certes, cependant, le conteneur IoC est transparent pour le reste de l'application. Vous l'appelez une fois au démarrage pour obtenir une instance, puis c'est la dernière que vous voyez. Avec les objets globaux, votre code est jonché de dépendances dures
Jeffrey Cameron
1
@qble utilise des usines pour créer des instances transitoires à la volée. Les usines sont des services uniques. Si les instances transitoires ont besoin de quelque chose du conteneur IOC, connectez-les à l'usine et demandez à l'usine de transmettre la dépendance à l'instance transitoire.
Jeffrey Cameron
14

Les frameworks IoC sont excellents si vous voulez ...

  • ... jetez la sécurité de type. De nombreux (tous?) Frameworks IoC vous obligent à exécuter le code si vous voulez être certain que tout est correctement connecté. "Hey! J'espère que j'ai tout configuré pour que mes initialisations de ces 100 classes n'échouent pas en production, levant des exceptions de pointeur nul!"

  • ... jonchent votre code de globaux (les frameworks IoC sont tous destinés à changer les états globaux).

  • ... écrivez du code pourri avec des dépendances peu claires qu'il est difficile de refactoriser car vous ne saurez jamais ce qui dépend de quoi.

Le problème avec l'IoC est que les personnes qui les utilisent écrivaient du code comme celui-ci

public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}

ce qui est évidemment imparfait car la dépendance entre Foo et Bar est câblée. Ensuite, ils ont réalisé qu'il serait préférable d'écrire du code comme

public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}

ce qui est également imparfait, mais moins évident. Dans Haskell, le type de Foo()serait, IO Foomais vous ne voulez vraiment pas la IOpartie-et devrait être un signe d'avertissement que quelque chose ne va pas avec votre conception si vous l'avez.

Pour s'en débarrasser (la partie IO), obtenez tous les avantages des frameworks IoC et aucun de ses inconvénients, vous pouvez utiliser une fabrique abstraite à la place.

La bonne solution serait quelque chose comme

data Foo = Foo { apa :: Bar }

ou peut-être

data Foo = forall b. (IBar b) => Foo { apa :: b }

et injecter (mais je n'appellerais pas cela injecter) Bar.

Aussi: voir cette vidéo avec Erik Meijer (inventeur de LINQ) où il dit que DI est pour les gens qui ne connaissent pas les mathématiques (et je ne pourrais pas être plus d'accord): http://www.youtube.com/watch?v = 8Mttjyf-8P4

Contrairement à M. Spolsky, je ne pense pas que les gens qui utilisent les frameworks IoC soient très intelligents - je crois simplement qu'ils ne connaissent pas les mathématiques.

Finnson
la source
9
jetez la sécurité de type - sauf que votre exemple est toujours sûr de type, c'est ce que l'interface est. Vous ne faites pas circuler les types d'objets, mais le type d'interface. Et la sécurité de type ne supprime pas les exceptions de référence nulle de toute façon, vous pouvez facilement passer des références nulles en tant que paramètres de sécurité de type - ce n'est pas du tout un problème de sécurité de type.
Blowdart
Vous jetez la sécurité des types car vous ne savez pas si le type est câblé ou non dans le conteneur IoC (le type IoC <IBar> ne l'est pas IBar, il l'est en fait IO IBar). Et oui, dans l'exemple Haskell Je ne peux pas obtenir de référence nulle.
finnsson
La sécurité de type ne concerne pas vraiment un objet câblé, du moins pas en Java / C #, il s'agit simplement que l'objet soit du bon type. Et ConcreteClass myClass = null est toujours du bon type. Si vous souhaitez appliquer non null, les contrats de code entrent en jeu.
Blowdart
2
Je suis d'accord avec votre 1er point dans une certaine mesure, j'aimerais une explication du 2ème et le 3ème n'a jamais été un problème pour moi. Votre exemple C # utilise le conteneur IoC comme localisateur de service, une erreur courante. Et en comparant C # vs Haskell = pommes vs oranges.
Mauricio Scheffer
2
Vous ne jetez pas la sécurité de type. Certains (sinon la plupart) conteneurs DI vous permettent de vérifier le graphique d'objet complet après le processus d'enregistrement. En faisant cela, vous pouvez empêcher l'application de démarrer sur une mauvaise configuration (échec rapide) et mes applications ont toujours quelques tests unitaires qui appellent la configuration de production pour me permettre de trouver ces erreurs pendant les tests. Je conseillerais à tous ceux qui utilisent un conteneur IoC d'écrire quelques tests unitaires pour s'assurer que tous les objets de niveau supérieur peuvent être résolus.
Steven
10

J'ai constaté que la mise en œuvre correcte de l'injection de dépendance tend à forcer les programmeurs à utiliser une variété d'autres pratiques de programmation qui aident à améliorer la testabilité, la flexibilité, la maintenabilité et l'évolutivité du code: des pratiques telles que le principe de responsabilité unique, les séparations de préoccupations et le codage contre les API. J'ai l'impression d'être obligé d'écrire des classes et des méthodes plus modulaires et plus petites, ce qui rend le code plus facile à lire, car il peut être pris en morceaux de petite taille.

Mais il a également tendance à créer des arborescences de dépendances assez grandes, qui sont beaucoup plus faciles à gérer via un framework (surtout si vous utilisez des conventions) qu'à la main. Aujourd'hui, je voulais tester quelque chose très rapidement dans LINQPad, et je me suis dit que ce serait trop compliqué de créer un noyau et de le charger dans mes modules, et j'ai fini par écrire ceci à la main:

var merger = new SimpleWorkflowInstanceMerger(
    new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
    new WorkflowAnswerRowUtil(
        new WorkflowFieldAnswerEntMapper(),
        new ActivityFormFieldDisplayInfoEntMapper(),
        new FieldEntMapper()),
    new AnswerRowMergeInfoRepository());

Rétrospectivement, il aurait été plus rapide d'utiliser le framework IoC, car les modules définissent à peu près tout cela par convention.

Après avoir passé un certain temps à étudier les réponses et les commentaires sur cette question, je suis convaincu que les personnes opposées à l'utilisation d'un conteneur IoC ne pratiquent pas l'injection de véritable dépendance. Les exemples que j'ai vus sont des pratiques qui sont souvent confondues avec l'injection de dépendance. Certaines personnes se plaignent de difficultés à "lire" le code. Si cela est fait correctement, la grande majorité de votre code devrait être identique lorsque vous utilisez DI à la main comme lorsque vous utilisez un conteneur IoC. La différence devrait résider entièrement dans quelques "points de lancement" au sein de l'application.

En d'autres termes, si vous n'aimez pas les conteneurs IoC, vous ne faites probablement pas l'injection de dépendances comme cela est censé être fait.

Un autre point: l'injection de dépendances ne peut vraiment pas être faite à la main si vous utilisez la réflexion n'importe où. Bien que je déteste ce que fait la réflexion pour coder la navigation, vous devez reconnaître qu'il y a certains domaines où cela ne peut vraiment pas être évité. ASP.NET MVC, par exemple, tente d'instancier le contrôleur via une réflexion sur chaque demande. Pour faire l'injection de dépendances à la main, vous devez faire de chaque contrôleur une "racine de contexte", comme ceci:

public class MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController()
    {
        _simpleMerger = new SimpleWorkflowInstanceMerger(
            new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
            new WorkflowAnswerRowUtil(
                new WorkflowFieldAnswerEntMapper(),
                new ActivityFormFieldDisplayInfoEntMapper(),
                new FieldEntMapper()),
            new AnswerRowMergeInfoRepository())
    }
    ...
}

Comparez maintenant cela avec le fait de permettre à un framework DI de le faire pour vous:

public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}

À l'aide d'un cadre DI, notez que:

  • Je peux tester à l'unité cette classe. En créant une maquette ISimpleWorkflowInstanceMerger, je peux tester qu'elle est utilisée de la manière que j'anticipe, sans avoir besoin d'une connexion à la base de données ou quoi que ce soit.
  • J'utilise beaucoup moins de code et le code est beaucoup plus facile à lire.
  • Si l'une des modifications de la dépendance de ma dépendance, je n'ai pas à apporter de modifications au contrôleur. C'est particulièrement agréable lorsque vous considérez que plusieurs contrôleurs sont susceptibles d'utiliser certaines des mêmes dépendances.
  • Je ne référence jamais explicitement des classes de ma couche de données. Mon application web peut simplement inclure une référence au projet contenant l' ISimpleWorkflowInstanceMergerinterface. Cela me permet de diviser l'application en modules séparés et de maintenir une véritable architecture à plusieurs niveaux, ce qui rend les choses beaucoup plus flexibles.

Une application Web typique aura plusieurs contrôleurs. Toute la douleur de faire DI à la main dans chaque contrôleur s'accumulera vraiment à mesure que votre application se développera. Si vous avez une application avec une seule racine de contexte, qui n'essaie jamais d'instancier un service par réflexion, ce n'est pas un problème aussi important. Néanmoins, toute application qui utilise l'injection de dépendance deviendra extrêmement coûteuse à gérer une fois qu'elle atteindra une certaine taille, à moins que vous n'utilisiez un cadre quelconque pour gérer le graphique de dépendance.

StriplingWarrior
la source
9

Chaque fois que vous utilisez le mot-clé "new", vous créez une dépendance de classe concrète et une petite alarme devrait retentir dans votre tête. Il devient plus difficile de tester cet objet isolément. La solution consiste à programmer des interfaces et à injecter la dépendance afin que l'objet puisse être testé unitaire avec tout ce qui implémente cette interface (par exemple, des simulations).

Le problème est que vous devez construire des objets quelque part. Un modèle d'usine est une façon de déplacer le couplage hors de vos POXO (Plain Old "insérez votre langage OO ici" Objets). Si vous et vos collègues écrivez tous du code comme celui-ci, un conteneur IoC est la prochaine "amélioration incrémentielle" que vous pouvez apporter à votre base de code. Il déplacera tout ce méchant code passe-partout Factory de vos objets propres et de votre logique métier. Ils l'obtiendront et l'adoreront. Heck, donnez une conférence sur pourquoi vous l'aimez et obtenez tout le monde enthousiasmé.

Si vos collègues ne font pas encore DI, alors je vous suggère de vous concentrer sur cela en premier. Passez le mot sur la façon d'écrire du code propre facilement testable. Le code DI propre est la partie difficile, une fois que vous y êtes, le déplacement de la logique de câblage d'objet des classes Factory vers un conteneur IoC devrait être relativement trivial.

brûlures mates
la source
8

Parce que toutes les dépendances sont clairement visibles, il favorise la création de composants qui sont couplés de manière lâche et en même temps facilement accessibles et réutilisables dans l'application.

bbmud
la source
7

Vous n'avez pas besoin d' un conteneur IoC.

Mais si vous suivez rigoureusement un modèle DI, vous constaterez qu'en avoir un supprimera une tonne de code redondant et ennuyeux.

C'est souvent le meilleur moment pour utiliser une bibliothèque / un framework, de toute façon - quand vous comprenez ce qu'il fait et que vous pourriez le faire sans la bibliothèque.

kyoryu
la source
6

Il se trouve que je suis en train de retirer le code DI local et de le remplacer par un IOC. J'ai probablement supprimé bien plus de 200 lignes de code et l'ai remplacé par environ 10. Oui, j'ai dû apprendre un peu comment utiliser le conteneur (Winsor), mais je suis ingénieur travaillant sur les technologies Internet dans le 21e siècle, donc je suis habitué à cela. J'ai probablement passé environ 20 minutes à regarder les procédures. Cela valait bien mon temps.

Matt Wrock
la source
3
"mais je suis un ingénieur travaillant sur les technologies Internet au 21ème siècle, donc j'y suis habitué" -> +1
user96403
5

Tandis que vous continuez à découpler vos classes et à inverser vos dépendances, les classes restent petites et le "graphique des dépendances" continue de grossir. (Ce n'est pas mauvais.) L'utilisation des fonctionnalités de base d'un conteneur IoC rend le câblage de tous ces objets trivial, mais le faire manuellement peut devenir très lourd. Par exemple, que se passe-t-il si je veux créer une nouvelle instance de "Foo" mais qu'il a besoin d'un "Bar". Et un "Bar" a besoin d'un "A", "B" et "C". Et chacun d'eux a besoin de 3 autres choses, etc etc. (oui, je ne peux pas trouver de bons faux noms :)).

L'utilisation d'un conteneur IoC pour créer votre graphique d'objet à votre place réduit la complexité d'une tonne et le pousse dans une configuration unique. Je dis simplement "créez-moi un" Foo "" et il comprend ce qui est nécessaire pour en construire un.

Certaines personnes utilisent les conteneurs IoC pour beaucoup plus d'infrastructures, ce qui est bien pour les scénarios avancés, mais dans ces cas, je conviens que cela peut obscurcir et rendre le code difficile à lire et à déboguer pour les nouveaux développeurs.

Aaron Lerch
la source
1
pourquoi continuons-nous d'apaiser le plus petit dénominateur commun plutôt que de travailler pour les relever?
stevenharman
4
Je n'ai rien dit sur l'apaisement. Je viens de dire que les scénarios avancés peuvent rendre les choses plus obscures pour les nouveaux développeurs - c'est un fait. Je n'ai pas dit "alors ne le fais pas". Mais même alors (il est temps pour l'avocat du diable), il existe souvent d'autres façons d'accomplir la même chose qui rendent une base de code plus explicite et accessible. Cacher la complexité de la configuration personnalisée de votre outil d'infrastructure simplement parce que vous le pouvez n'est pas la bonne raison, et cela ne tire personne.
Aaron Lerch
4

Idem sur l'unité. Devenez trop gros et vous pouvez entendre les grincements dans les chevrons.

Cela ne me surprend jamais lorsque les gens commencent à parler de la netteté du code IoC sont les mêmes types de gens qui ont dit à un moment donné que les modèles en C ++ étaient la manière élégante de revenir dans les années 90, mais aujourd'hui, ils les décriront comme des arcanes . Bah!

M Lopez
la source
2

Dans le monde .NET, l'AOP n'est pas trop populaire, donc pour DI un framework est votre seule vraie option, que vous en écriviez vous-même ou que vous utilisiez un autre framework.

Si vous avez utilisé AOP, vous pouvez effectuer une injection lorsque vous compilez votre application, ce qui est plus courant en Java.

L'ID présente de nombreux avantages, tels qu'un couplage réduit, ce qui facilite les tests unitaires, mais comment allez-vous l'implémenter? Voulez-vous utiliser la réflexion pour le faire vous-même?

James Black
la source
Le nouveau cadre MEF permet cela pour .NET et je dois dire que le code est plus propre et plus facile à comprendre, ce qui facilite beaucoup la compréhension du concept DI.
terjetyl
4
@TT: MEF n'est pas un conteneur d'injection de dépendances et n'a rien à voir avec AOP.
Mauricio Scheffer
2

Ça fait presque 3 ans, hein?

  1. 50% de ceux qui se sont prononcés en faveur des cadres IoC ne comprennent pas la différence entre IoC et IoC Frameworks. Je doute qu'ils sachent que vous pouvez écrire du code sans être déployé sur un serveur d'applications

  2. Si nous prenons le framework Java Spring le plus populaire, c'est la configuration IoC déplacée de XMl dans le code et qui ressemble maintenant à ceci

`@Configuration classe publique AppConfig {

public @Bean TransferService transferService() {
    return new TransferServiceImpl(accountRepository());
}

public @Bean AccountRepository accountRepository() {
    return new InMemoryAccountRepository();
}

} `Et nous avons besoin d'un cadre pour faire cela, pourquoi exactement?

Ivan
la source
1

Honnêtement, je ne trouve pas qu'il y ait de nombreux cas où des conteneurs IoC sont nécessaires, et la plupart du temps, ils ajoutent simplement une complexité inutile.

Si vous l'utilisez uniquement pour simplifier la construction d'un objet, je dois vous demander si vous instanciez cet objet à plusieurs endroits? Un singleton ne conviendrait-il pas à vos besoins? Modifiez-vous la configuration au moment de l'exécution? (Changement de type de source de données, etc.).

Si oui, alors vous pourriez avoir besoin d'un conteneur IoC. Sinon, vous déplacez simplement l'initialisation de l'endroit où le développeur peut facilement la voir.

Qui a dit qu'une interface vaut mieux que l'héritage de toute façon? Imaginons que vous testiez un service. Pourquoi ne pas utiliser le constructeur DI et créer des simulations des dépendances en utilisant l'héritage? La plupart des services que j'utilise n'ont que quelques dépendances. Faire des tests unitaires de cette façon empêche de maintenir une tonne d'interfaces inutiles et signifie que vous n'avez pas besoin d'utiliser Resharper pour trouver rapidement la déclaration d'une méthode.

Je crois que pour la plupart des implémentations, dire que les conteneurs IoC suppriment le code inutile est un mythe.

Tout d'abord, il y a la configuration du conteneur en premier lieu. Il vous reste alors à définir chaque objet à initialiser. Donc, vous ne sauvegardez pas le code lors de l'initialisation, vous le déplacez (sauf si votre objet est utilisé plus d'une fois. Est-ce mieux en tant que Singleton?). Ensuite, pour chaque objet que vous avez initialisé de cette manière, vous devez créer et maintenir une interface.

Quelqu'un a un avis là dessus?

Fred
la source
Même pour les petits projets, mettre la configuration en XML le rend tellement plus propre et plus modulaire - et pour la première fois, rend le code réutilisable (car il n'est plus contaminé par de la colle). Pour les produits logiciels appropriés, vous pouvez configurer ou échanger par exemple ShippingCostCalculatorou ProductUIPresentation, ou toute autre stratégie / implémentation, dans la même base de code exacte , en déployant un seul fichier XML modifié.
Thomas W
Si c'est implémenté bon gré mal gré, en général, je pense que vous ajoutez plus de complexité, pas moins. Lorsque vous devez échanger quelque chose au moment de l'exécution, ils sont parfaits, mais pourquoi ajouter l'obscurcissement? Pourquoi garder la surcharge des interfaces supplémentaires pour des choses qui ne seront jamais désactivées? Je ne dis pas de ne pas utiliser l'IoC pour les tests. Par tous les moyens, utilisez l'injection de dépendances de propriété ou même de constructeur. Avez-vous besoin d'utiliser une interface? Pourquoi ne pas sous-classer vos objets de test? Ne serait-ce pas plus propre?
Fred
Vous n'avez pas du tout besoin d'interfaces pour faire la configuration XML / IoC. J'ai trouvé de grandes améliorations de clarté dans un petit projet avec seulement ~ 8 pages et 5 ou 6 services. Il y a moins d'obscurcissement car les services et les contrôleurs n'ont plus besoin de code de colle.
Thomas W
1

Vous auriez besoin d'un conteneur IoC si vous aviez besoin de centraliser la configuration de vos dépendances afin qu'elles puissent être facilement échangées en masse. Cela a plus de sens dans TDD, où de nombreuses dépendances sont permutées, mais où il y a peu d'interdépendance entre les dépendances. Cela se fait au prix d'obscurcir le flux de contrôle de la construction des objets dans une certaine mesure, il est donc important d'avoir une configuration bien organisée et raisonnablement documentée. Il est également bon d'avoir une raison de le faire, sinon, c'est de la dorure à l'abstraction . Je l'ai vu si mal fait qu'il a été traîné jusqu'à être l'équivalent d'une instruction goto pour les constructeurs.

DaveMorganTexas
la source
1

Voici pourquoi. Le projet s'appelle IOC-with-Ninject. Vous pouvez le télécharger et l'exécuter avec Visual Studio. Cet exemple utilise Ninject mais TOUTES les «nouvelles» instructions sont au même endroit et vous pouvez complètement changer la façon dont votre application s'exécute en changeant le module de liaison à utiliser. L'exemple est configuré pour que vous puissiez vous lier à une version simulée des services ou à la version réelle. Dans les petits projets qui peuvent ne pas avoir d'importance, mais dans les grands projets, c'est une grosse affaire.

Juste pour être clair, les avantages tels que je les vois: 1) TOUTES les nouvelles instructions au même endroit à la racine du code. 2) Code totalement re-factorisé avec un changement. 3) Points supplémentaires pour `` facteur cool '' car c'est ... eh bien: cool. : p

CodeChops
la source
1

Je vais essayer de comprendre pourquoi le CIO pourrait ne pas être bon pour moi.

Comme pour tout le reste, le conteneur IOC (ou comme le dirait Einstein I = OC ^ 2) est un concept que vous devez décider vous-même si vous en avez besoin ou non dans votre code. Le tollé récent sur la mode à propos d'IOC n'est que cela, la mode. Ne tombez pas dans la mode, c'est le premier. Il existe une myriade de concepts que vous pouvez implémenter dans votre code. Tout d'abord, j'utilise l'injection de dépendance depuis que j'ai commencé la programmation et j'ai appris le terme lui-même quand il a été popularisé sous ce nom. Le contrôle de la dépendance est un sujet très ancien et il a été traité jusqu'à présent de milliers de milliards de façons, en fonction de ce qui se découplait de quoi. Découpler tout de tout est un non-sens. Le problème avec le conteneur IOC est qu'il essaie d'être aussi utile qu'Entity Framework ou NHibernate. Bien que l'écriture d'un mappeur relationnel-objet soit tout simplement indispensable dès que vous devez coupler une base de données avec votre système, le conteneur IOC n'est pas toujours nécessaire. Ainsi, lorsque le conteneur IOC est utile:

  1. Lorsque vous avez une situation avec de nombreuses dépendances que vous souhaitez organiser
  2. Lorsque vous ne vous souciez pas de coupler votre code avec un produit tiers
  3. Lorsque vos développeurs veulent apprendre à travailler avec un nouvel outil

1: Ce n'est pas si souvent que vous avez autant de dépendances dans votre code, ou que vous les connaissez au début de la conception. La pensée abstraite est utile lorsque la pensée abstraite est due.

2: Le couplage de votre code avec un code tiers est un problème HuGe. Je travaillais avec du code qui a plus de 10 ans et qui suivait à l'époque des concepts sophistiqués et avancés ATL, COM, COM + et ainsi de suite. Il n'y a rien que vous puissiez faire avec ce code maintenant. Ce que je dis, c'est qu'un concept avancé donne un avantage apparent, mais il est annulé à long terme avec l'avantage obsolète lui-même. Cela avait simplement rendu tout cela plus cher.

3: Le développement logiciel est déjà assez difficile. Vous pouvez l'étendre à des niveaux méconnaissables si vous autorisez un concept avancé à rogner dans votre code. Il y a un problème avec IOC2. Même s'il s'agit de découpler des dépendances, il découplera également le flux logique. Imaginez que vous avez trouvé un bug et que vous devez définir une pause pour examiner la situation. L'IOC2, comme tout autre concept avancé, rend cela plus difficile. La correction d'un bogue dans un concept est plus difficile que la correction d'un bogue dans un code plus simple, car lorsque vous corrigez un bogue, un concept doit être à nouveau respecté. (Juste pour vous donner un exemple, C ++ .NET change constamment la syntaxe à tel point que vous devez bien réfléchir avant de refactoriser une ancienne version de .NET.) Quel est donc le problème avec IOC? Le problème réside dans la résolution des dépendances. La logique de résolution est généralement cachée dans l'IOC2 lui-même, écrit peut-être d'une manière inhabituelle que vous devez apprendre et maintenir. Votre produit tiers sera-t-il disponible dans 5 ans? Microsoft ne l'était pas.

Le syndrome "Nous savons comment" est écrit un peu partout concernant IOC2. Ceci est similaire aux tests d'automatisation. Terme de fantaisie et solution parfaite à première vue, vous mettez simplement tous vos tests à exécuter pendant la nuit et voyez les résultats le matin. Il est vraiment pénible d'expliquer entreprise après entreprise ce que signifient vraiment les tests automatisés. Les tests automatisés ne sont certainement pas un moyen rapide de réduire le nombre de bogues que vous pouvez introduire du jour au lendemain pour augmenter la qualité de votre produit. Mais, la mode rend cette notion énormément dominante. IOC2 souffre du même syndrome. On pense que vous devez l'implémenter pour que votre logiciel soit bon. Tous les entretiens récents, on m'a demandé si j'implémentais IOC2 et l'automatisation. C'est un signe de mode: l'entreprise avait une partie du code écrite en MFC qu'elle n'abandonnera pas.

Vous devez apprendre IOC2 comme tout autre concept logiciel. La décision d'utiliser ou non l'IOC2 appartient à l'équipe et à l'entreprise. Cependant, au moins TOUS les arguments ci-dessus doivent être mentionnés avant que la décision ne soit prise. Ce n'est que si vous voyez que le côté positif l'emporte sur le côté négatif que vous pouvez prendre une décision positive.

Il n'y a rien de mal avec IOC2, sauf qu'il ne résout que les problèmes qu'il résout et présente les problèmes qu'il présente. Rien d'autre. Cependant, aller à l'encontre de la mode est très difficile, ils ont la bouche en sueur, les adeptes de tout. C'est étrange comme aucun d'eux n'est là quand le problème de leur fantaisie devient apparent. De nombreux concepts dans l'industrie du logiciel ont été défendus parce qu'ils génèrent du profit, des livres sont écrits, des conférences ont lieu, de nouveaux produits sont fabriqués. C'est la mode, généralement de courte durée. Dès que les gens trouvent autre chose, ils l'abandonnent complètement. IOC2 est utile mais il montre les mêmes signes que beaucoup d'autres concepts disparus que j'ai vus. Je ne sais pas si elle survivra. Il n'y a pas de règle pour cela. Vous pensez que si elle est utile, elle survivra. Non, ça ne marche pas comme ça. Une grande entreprise riche suffit et le concept peut mourir en quelques semaines. Nous verrons. NHibernate a survécu, EF est arrivé deuxième. Peut-être que l'IOC2 survivra aussi. N'oubliez pas que la plupart des concepts en développement logiciel n'ont rien de spécial, ils sont très logiques, simples et évidents, et il est parfois plus difficile de se souvenir de la convention d'appellation actuelle que de comprendre le concept lui-même. La connaissance d'IOC2 fait-elle d'un développeur un meilleur développeur? Non, car si un développeur n'a pas été en mesure de proposer un concept de nature similaire à IOC2, il lui sera difficile de comprendre quel problème IOC2 résout, son utilisation semblera artificielle et il ou elle pourra commencer à l'utiliser. par souci d'être une sorte de politiquement correct. N'oubliez pas que la plupart des concepts en développement logiciel n'ont rien de spécial, ils sont très logiques, simples et évidents, et il est parfois plus difficile de se souvenir de la convention d'appellation actuelle que de comprendre le concept lui-même. La connaissance d'IOC2 fait-elle d'un développeur un meilleur développeur? Non, car si un développeur n'a pas été en mesure de proposer un concept de nature similaire à IOC2, il lui sera difficile de comprendre quel problème IOC2 résout, son utilisation semblera artificielle et il ou elle pourra commencer à l'utiliser. par souci d'être une sorte de politiquement correct. N'oubliez pas que la plupart des concepts en développement logiciel n'ont rien de spécial, ils sont très logiques, simples et évidents, et il est parfois plus difficile de se souvenir de la convention d'appellation actuelle que de comprendre le concept lui-même. La connaissance d'IOC2 fait-elle d'un développeur un meilleur développeur? Non, car si un développeur n'a pas été en mesure de proposer un concept de nature similaire à IOC2, il lui sera difficile de comprendre quel problème IOC2 résout, son utilisation semblera artificielle et il ou elle pourra commencer à l'utiliser. par souci d'être une sorte de politiquement correct. La connaissance d'IOC2 fait-elle d'un développeur un meilleur développeur? Non, car si un développeur n'a pas été en mesure de proposer un concept de nature similaire à IOC2, il lui sera difficile de comprendre quel problème IOC2 résout, son utilisation semblera artificielle et il ou elle pourra commencer à l'utiliser. par souci d'être une sorte de politiquement correct. La connaissance d'IOC2 fait-elle d'un développeur un meilleur développeur? Non, car si un développeur n'a pas été en mesure de proposer un concept de nature similaire à IOC2, il lui sera difficile de comprendre quel problème IOC2 résout, son utilisation semblera artificielle et il ou elle pourra commencer à l'utiliser. par souci d'être une sorte de politiquement correct.

user1228608
la source
0

Personnellement, j'utilise IoC comme une sorte de carte de structure de mon application (Ouais, je préfère également StructureMap;)). Il est facile de remplacer mes implémentations d'interface habituelles par des implémentations Moq pendant les tests. Créer une configuration de test peut être aussi simple que de faire un nouvel appel init à mon framework IoC, en remplaçant la classe qui est ma limite de test par une maquette.

Ce n'est probablement pas ce pour quoi l'IoC est là, mais c'est ce que je me retrouve à utiliser le plus.

cwap
la source
0

Je me rends compte que c'est un poste assez ancien, mais il semble encore raisonnablement actif, et j'ai pensé apporter quelques points qui n'ont pas encore été mentionnés dans d'autres réponses.

Je dirai que je suis d'accord avec les avantages de l'injection de dépendance, mais je préfère construire et gérer les objets moi-même, en utilisant un modèle similaire à celui décrit par Maxm007 dans sa réponse. J'ai trouvé deux problèmes principaux avec l'utilisation de conteneurs tiers:

1) Le fait de disposer d'une bibliothèque tierce pour gérer la durée de vie de vos objets de manière "automagique" peut se prêter à des résultats inattendus. Nous avons constaté que, en particulier dans les grands projets, vous pouvez avoir beaucoup plus de copies d'un objet que vous attendez, et plus que si vous gériez manuellement les cycles de vie. Je suis sûr que cela varie en fonction du cadre utilisé, mais le problème existe néanmoins. Cela peut également être problématique si votre objet contient des ressources, des connexions de données, etc., car l'objet peut parfois vivre plus longtemps que prévu. Donc, inévitablement, les conteneurs IoC ont tendance à augmenter l'utilisation des ressources et l'empreinte mémoire d'une application.

2) Les conteneurs IoC, à mon avis, sont une forme de "programmation en boîte noire". J'ai constaté en particulier que nos développeurs moins expérimentés ont tendance à en abuser. Il permet au programmeur de ne pas avoir à réfléchir à la façon dont les objets doivent être liés les uns aux autres ou à les découpler, car il leur fournit un mécanisme dans lequel ils peuvent simplement saisir n'importe quel objet de leur choix. Par exemple, il peut y avoir une bonne raison de conception pour laquelle ObjectA ne devrait jamais connaître ObjectB directement, mais plutôt que de créer une usine ou un pont ou un localisateur de service, un programmeur inexpérimenté dira simplement "pas de problème, je vais simplement récupérer ObjectB du conteneur IoC ". Cela peut en fait conduire à un couplage d'objet accru, ce que l'IoC est censé aider à prévenir.

amnésie
la source
-8

L'injection de dépendances dans un projet ASP.NET peut être effectuée avec quelques lignes de code. Je suppose qu'il y a un avantage à utiliser un conteneur lorsque vous avez une application qui utilise plusieurs frontaux et a besoin de tests unitaires.

etechpartner
la source
1
Pouvez-vous donner un exemple? En quoi DI est-il différent dans ASP.NET par rapport à tout autre type d'application?
tofi9