Question Pourquoi ai-je besoin d'un conteneur IoC plutôt que d'un simple code DI? [fermé]


J'ai utilisé Injection de dépendance (DI) pendant 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 Inversion de contrôle (IoC) conteneur. Cependant, plus je lis, plus je ressens de la pression de la part de la communauté pour utiliser un conteneur IoC.

J'ai joué avec des conteneurs .NET comme StructureMap, NInject, Unité, 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.

S'il vous plaît, me convaincre que je dois utiliser un conteneur IoC. Je vais utiliser ces arguments quand je parle à mes collègues développeurs au travail.


598


origine


Réponses:


Wow, je ne peux pas croire que Joel serait en faveur de 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 se rendent pas compte que votre chaîne de dépendances peut s'imbriquer, et il devient rapidement difficile de les connecter manuellement. Même avec les 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) vous demande d'implémenter INotifyPropertyChanged afin de pouvoir effectuer le suivi des modifications et mettre à jour l'interface utilisateur en conséquence.

"OK, ça ne sonne pas si fort" alors vous commencez à écrire.

Vous commencez avec 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 se retrouvent avec ce:

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 de votre client. Il y a une meilleure façon de travailler, plus intelligente.

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

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

Si vous rendez vos propriétés virtuelles (calmez-vous, ce n'est pas un gros problème) alors nous pouvons tisser dans ce comportement de propriété automatiquement. (Ceci est appelé 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());

Poof!  Tout ce manuel BS INotifyPropertyChanged est maintenant généré automatiquement pour vous, sur chaque setter 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, alors vous pouvez ignorer toute cette propriété en enveloppant mumbo-jumbo. Vous avez des problèmes d'affaires à résoudre.

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

  • Transactions de base de données déclaratives et imbriquées
  • Unité de travail déclarative et imbriquée
  • Enregistrement
  • Pré / Post conditions (Conception par contrat)

441



Je suis avec toi, Vadim. Les conteneurs IoC adoptent 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 quant à la façon dont la communauté de l'IoC a pris une belle, article élégant de Martin Fowler et l'a transformé en un tas de cadres complexes typiquement avec des manuels de 200-300 pages.

J'essaie de ne pas juger (HAHA!), Mais je pense que les gens qui utilisent des conteneurs IoC sont (A) très intelligents et (B) manquent d'empathie pour les gens qui ne sont pas aussi intelligents qu'eux. Tout leur semble parfaitement logique, ils ont donc du mal à comprendre que beaucoup de programmeurs ordinaires trouveront les concepts déroutants. C'est le malédiction de la connaissance. Les gens qui comprennent les conteneurs de l'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 de production. Supposons, par exemple, que vous disposiez de deux versions de vos classes d'accès à la base de données: une version qui enregistrait de façon agressive et beaucoup de validation, que vous utilisiez lors du développement et une autre version sans journalisation ou validation qui était incroyablement rapide pour la production. C'est agréable de pouvoir passer d'un endroit à l'autre. D'un autre côté, il s'agit d'un problème assez trivial, facile à gérer, 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 s'écrie.


138



On suppose que 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 ainsi 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ù le DI manuel devient une corvée (c'est-à-dire augmente la maintenance), pesez-le par rapport à la courbe d'apprentissage en é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 pattern 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. S'en tenir à l'injection du constructeur. Les bases de Unity ou de StructureMap peuvent être condensées sur quelques pages.

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


37



À mon avis, le principal avantage 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 utilisiez une classe IoC statique, par opposition à l'IMHO, les fichiers de configuration 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.
   }
}

32



Les conteneurs IoC sont également utiles pour le chargement de dépendances de classes profondément imbriquées. Par exemple si vous avez eu le code suivant en utilisant Injection de Deperence.

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 avez chargé toutes ces dépendances dans le conteneur IoC, vous pouvez résoudre le CustomerService 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.
}

32



Je suis un fan de la programmation déclarative (regardez combien de questions SQL je réponds), mais les conteneurs IoC que j'ai regardé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 concept d'un conteneur IoC est mauvais. Mais la mise en œuvre doit être 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 sur une et une demi-douzaine de l'autre. Une application réelle (pas un jouet ou une démo) est forcément complexe, ce qui explique de nombreux cas particuliers et des exceptions aux règles. Soit vous encapsulez cette complexité dans le code impératif, soit dans le code déclaratif. Mais vous devez le représenter quelque part.


28



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

  • Réduire boule de poils routines de démarrage du programme principal.
  • Permettre des capacités de reconfiguration en temps de déploiement assez importantes.
  • Faire du style dépendance-injectable le chemin de moindre résistance pour un nouveau travail.

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 naviguer dans les problèmes personnels, de processus et de culture d'équipe - mais alors, c'est pourquoi vous avez demandé ...
  • Certains des kits d'outils sont en train de devenir très lourds eux-mêmes, ce qui encourage le type de dépendance profonde que de nombreux conteneurs DI ont commencé à déclencher.

27