598 Stimmen

Warum brauche ich einen IoC-Container im Gegensatz zu einfachem DI-Code?

Ich habe mit Injektion von Abhängigkeiten (DI) für eine Weile, entweder in einem Konstruktor, einer Eigenschaft oder einer Methode. Ich hatte noch nie das Bedürfnis, eine Umkehrung der Kontrolle (IoC) Container. Je mehr ich jedoch lese, desto mehr Druck verspüre ich von der Gemeinschaft, einen IoC-Container zu verwenden.

Ich spielte mit .NET-Containern wie StructureMap , NInject , Einigkeit y Funq . Ich kann immer noch nicht sehen, wie ein IoC-Container zu profitieren / verbessern meinen Code.

Ich habe auch Angst davor, bei der Arbeit einen Container zu verwenden, weil viele meiner Kollegen Code sehen werden, den sie nicht verstehen. Viele von ihnen sind vielleicht nicht bereit, eine neue Technologie zu erlernen.

Bitte, überzeugen Sie mich, dass ich einen IoC-Container verwenden muss. Ich werde diese Argumente verwenden, wenn ich mit meinen Entwicklerkollegen bei der Arbeit spreche.

440voto

Ben Scheirman Punkte 39742

Wow, ich kann nicht glauben, dass Joel so etwas befürworten würde:

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

darüber:

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

Viele Leute sind sich nicht bewusst, dass die Kette der Abhängigkeiten verschachtelt werden kann und es schnell unhandlich wird, sie manuell zu verdrahten. Selbst bei Fabriken lohnt sich die Verdoppelung des Codes einfach nicht.

IoC-Container können komplex sein, ja. Aber für diesen einfachen Fall habe ich gezeigt, dass es unglaublich einfach ist.


Okay, rechtfertigen wir das noch weiter. Nehmen wir an, Sie haben einige Entitäten oder Modellobjekte, die Sie an eine intelligente Benutzeroberfläche binden möchten. Diese intelligente Benutzeroberfläche (nennen wir sie Shindows Morms) möchte, dass Sie INotifyPropertyChanged implementieren, damit sie Änderungen verfolgen und die Benutzeroberfläche entsprechend aktualisieren kann.

"OK, das klingt gar nicht so schwer", und Sie beginnen zu schreiben.

Sie beginnen damit:

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

und enden mit este :

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;
}

Das ist ekelhafter Klempnercode, und ich behaupte, wenn man so einen Code von Hand schreibt Sie beklauen Ihren Kunden . Es gibt bessere, intelligentere Arbeitsmethoden.

Kennen Sie den Begriff "intelligenter arbeiten, nicht härter"?

Stellen Sie sich vor, ein kluger Kopf aus Ihrem Team kommt und sagt: "Es gibt einen einfacheren Weg"

Wenn Sie Ihre Immobilien virtuell machen (beruhigen Sie sich, das ist keine große Sache), können wir weben in dieser Eigenschaft Verhalten automatisch. (Dies wird AOP genannt, aber machen Sie sich keine Gedanken über den Namen, sondern konzentrieren Sie sich darauf, was es für Sie tun wird)

Je nachdem, welches IoC-Tool Sie verwenden, könnten Sie etwas tun, das wie folgt aussieht:

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

Puff! All diese manuellen INotifyPropertyChanged BS werden nun automatisch für Sie generiert, und zwar für jeden virtuellen Property Setter des betreffenden Objekts.

Ist das Magie? YES ! Wenn Sie sich darauf verlassen können, dass dieser Code seine Aufgabe erfüllt, können Sie den ganzen Hokuspokus mit der Eigenschaftsumschreibung getrost auslassen. Sie haben geschäftliche Probleme zu lösen.

Einige andere interessante Anwendungen eines IoC-Tools für AOP:

  • Deklarative und verschachtelte Datenbanktransaktionen
  • Deklarativ & verschachtelt Arbeitseinheit
  • Protokollierung
  • Vor- und Nachbedingungen (Design by Contract)

138voto

Joel Spolsky Punkte 32916

Ich stimme dir zu, Vadim. IoC-Container nehmen ein einfaches, elegantes und nützliches Konzept und machen es zu etwas, das man zwei Tage lang mit einem 200-seitigen Handbuch studieren muss.

Ich persönlich bin verblüfft, wie die IoC-Gemeinschaft eine so schöne Sache gemacht hat, eleganter Artikel von Martin Fowler und machte daraus eine Reihe komplexer Rahmenwerke mit typischerweise 200-300 Seiten langen Handbüchern.

Ich versuche, nicht voreingenommen zu sein (HAHA!), aber ich denke, dass Leute, die IoC-Container verwenden, (A) sehr klug sind und (B) wenig Einfühlungsvermögen für Leute haben, die nicht so klug sind wie sie selbst. Für sie ergibt alles einen perfekten Sinn, so dass sie Schwierigkeiten haben zu verstehen, dass viele normale Programmierer die Konzepte verwirrend finden werden. Das ist die Fluch des Wissens . Diejenigen, die IoC-Container verstehen, haben Schwierigkeiten zu glauben, dass es Leute gibt, die sie nicht verstehen.

Der wertvollste Vorteil der Verwendung eines IoC-Containers besteht darin, dass Sie an einer Stelle einen Konfigurationsschalter haben, mit dem Sie z. B. zwischen Test- und Produktionsmodus wechseln können. Nehmen wir zum Beispiel an, Sie haben zwei Versionen Ihrer Datenbankzugriffsklassen... eine Version, die aggressiv protokolliert und eine Menge Validierungen durchführt, die Sie während der Entwicklung verwenden, und eine andere Version ohne Protokollierung oder Validierung, die für die Produktion verdammt schnell ist. Es ist schön, wenn man an einem Ort zwischen beiden Versionen wechseln kann. Andererseits handelt es sich um ein ziemlich triviales Problem, das ohne die Komplexität von IoC-Containern auf einfachere Weise gelöst werden kann.

Ich glaube, wenn Sie IoC-Container verwenden, wird Ihr Code, offen gesagt, viel schwerer zu lesen. Die Anzahl der Stellen, auf die Sie schauen müssen, um herauszufinden, was der Code zu tun versucht, steigt um mindestens eine. Und irgendwo im Himmel schreit ein Engel auf.

37voto

TrueWill Punkte 24357

Vermutlich zwingt Sie niemand dazu, ein DI-Container-Framework zu verwenden. Sie verwenden bereits DI, um Ihre Klassen zu entkoppeln und die Testbarkeit zu verbessern, so dass Sie viele der Vorteile erhalten. Kurz gesagt, Sie bevorzugen die Einfachheit, was im Allgemeinen eine gute Sache ist.

Wenn Ihr System einen Grad an Komplexität erreicht, bei dem manuelle DI zu einer lästigen Pflicht wird (d. h. die Wartung erhöht), sollten Sie dies gegen die Lernkurve eines DI-Container-Frameworks für das Team abwägen.

Wenn Sie mehr Kontrolle über die Verwaltung der Lebensdauer von Abhängigkeiten benötigen (d. h. wenn Sie das Singleton-Muster implementieren möchten), sollten Sie sich DI-Container ansehen.

Wenn Sie einen DI-Container verwenden, nutzen Sie nur die Funktionen, die Sie benötigen. Überspringen Sie die XML-Konfigurationsdatei und konfigurieren Sie sie im Code, wenn dies ausreichend ist. Bleiben Sie bei der Konstruktorinjektion. Die Grundlagen von Unity oder StructureMap lassen sich auf ein paar Seiten zusammenfassen.

Es gibt einen großartigen Blogbeitrag von Mark Seemann zu diesem Thema: Wann wird ein DI-Container verwendet?

32voto

bendewey Punkte 38830

IoC-Container eignen sich auch zum Laden tief verschachtelter Klassenabhängigkeiten. Zum Beispiel, wenn Sie den folgenden Code mit Depedency Injection hatten.

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 { }

Wenn Sie alle diese Abhängigkeiten in einen IoC-Container geladen haben, können Sie den CustomerService auflösen und alle untergeordneten Abhängigkeiten werden automatisch aufgelöst.

Zum Beispiel:

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.
}

32voto

bendewey Punkte 38830

Meiner Meinung nach ist der größte Vorteil eines IoC die Möglichkeit, die Konfiguration Ihrer Abhängigkeiten zu zentralisieren.

Wenn Sie derzeit Dependency Injection verwenden, könnte Ihr Code wie folgt aussehen

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

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

Wenn Sie eine statische IoC-Klasse verwenden, im Gegensatz zu den, IMHO verwirrenderen, Konfigurationsdateien, könnten Sie etwas wie dieses haben:

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
}

Dann würde Ihre statische IoC-Klasse wie folgt aussehen, ich verwende hier Unity.

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.
   }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X