Perché ho bisogno di un contenitore IoC rispetto al semplice codice DI? [chiuso]


598

Sto usando Dependency Injection (DI) per un po ', iniettando in un costruttore, proprietà o metodo. Non ho mai sentito il bisogno di usare un contenitore Inversion of Control (IoC). Tuttavia, più leggo, maggiore è la pressione che provo dalla comunità di utilizzare un contenitore IoC.

Ho giocato con container .NET come StructureMap , NInject , Unity e Funq . Non riesco ancora a vedere come un contenitore IoC possa beneficiare / migliorare il mio codice.

Ho anche paura di iniziare a usare un container al lavoro perché molti dei miei colleghi vedranno codice che non capiscono. Molti di loro potrebbero essere riluttanti ad apprendere nuove tecnologie.

Per favore, convincimi che devo usare un contenitore IoC. Userò questi argomenti quando parlerò con i miei colleghi sviluppatori al lavoro.


6
Buona domanda. Sto rispondendo a questo nei commenti poiché sono molto nuovo all'idea del CIO. Sembra l'idea di componenti plug and play e accoppiamento libero. Se hai bisogno di usare qualche altro componente al posto di quello attuale, è basato sulla necessità. L'uso del CIO consiste nel preparare il codice per tale modifica, se si presenta IMO.
Shahkalpesh,

3
@shahkalpesh ma posso ottenere un accoppiamento lento con un semplice DI. Tuttavia, vedo il tuo punto nel caso in cui utilizzo il file di configurazione. Tuttavia, non sono sicuro se mi piace usare il file di configurazione. I file di configurazione sono molto dettagliati, difficoltà nel refactoring e passaggio da un file all'altro.
Vadim,

110
semplicemente aggiungendo un commento poiché sembra che tu stia cercando ragioni per usare l'IoC principalmente; ma c'è qualcos'altro da dire sul tuo problema. Non puoi scrivere il tuo codice al livello della persona peggiore nella tua squadra o per paura che non vogliono imparare. Hai la responsabilità non solo di essere un professionista, ma anche del tuo futuro per crescere e il tuo team non dovrebbe trattenerti.
Kevin Sheffield,

4
@Kevin, attualmente stiamo usando l'IoC. Non è stato difficile come pensare insegnare a tutti l'IoC.
Vadim,

21
Non ho idea del perché i moderatori chiudano in maniera massiccia domande votate e utili. Forse in questo caso, Will non capisce la domanda o non capisce per cosa le persone usano stackexchange? Se "non è adatto al formato di domande e risposte di stackexchange", forse considera che la tua politica è sbagliata e non tutte queste centinaia di domande utili con centinaia di voti positivi e risposte utili votate positivamente.
NickG

Risposte:


441

Wow, non posso credere che Joel preferirebbe questo:

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

oltre questo:

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

Molte persone non si rendono conto che la catena delle dipendenze può essere nidificata e diventa rapidamente ingombrante collegarle manualmente. Anche con le fabbriche, la duplicazione del tuo codice non ne vale la pena.

I contenitori IoC possono essere complessi, sì. Ma per questo semplice caso ho dimostrato che è incredibilmente facile.


Va bene, giustificiamo ancora di più. Supponiamo che tu abbia alcune entità o oggetti modello che desideri associare a un'interfaccia utente intelligente. Questa interfaccia utente intelligente (la chiameremo Shindows Morms) vuole che tu attui INotifyPropertyChanged in modo che possa effettuare il rilevamento delle modifiche e aggiornare l'interfaccia utente di conseguenza.

"OK, non sembra così difficile" così inizi a scrivere.

Inizi con questo:

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

..e finiamo con questo :

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

È un disgustoso codice idraulico, e sostengo che se stai scrivendo a mano un codice del genere, stai rubando al tuo cliente . Esistono modi migliori e più intelligenti di lavorare.

Hai mai sentito quel termine, lavorare in modo più intelligente, non più difficile?

Immagina che un ragazzo intelligente della tua squadra sia venuto e abbia detto: "Ecco un modo più semplice"

Se rendi le tue proprietà virtuali (calmati, non è un grosso problema) allora possiamo intrecciare automaticamente quel comportamento di proprietà. (Questo si chiama AOP, ma non preoccuparti per il nome, concentrati su cosa farà per te)

A seconda dello strumento IoC che stai utilizzando, potresti fare qualcosa del genere:

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

Puf! Tutto quel manuale INotifyPropertyChanged BS viene ora generato automaticamente per te, su ogni setter di proprietà virtuale dell'oggetto in questione.

Questa è magia? ! Se puoi fidarti del fatto che questo codice fa il suo lavoro, puoi saltare in sicurezza tutta quella proprietà che avvolge mumbo-jumbo. Hai problemi di business da risolvere.

Alcuni altri usi interessanti di uno strumento IoC per fare AOP:

  • Transazioni di database dichiarative e nidificate
  • Unità di lavoro dichiarativa e nidificata
  • Registrazione
  • Condizioni pre / post (progettazione per contratto)

28
Spiacenti, hai dimenticato di rendere virtuali tutte le proprietà e non ha funzionato. È il tempo necessario per il debug di questo furto anche dal tuo cliente? (Mi scuso se non stai usando DynamicProxy.: P)
Thom

17
Sacrifichi MOLTA chiarezza usando il wrapper INotifyPropertyChanged. Se impieghi più di qualche minuto a scrivere quell'impianto idraulico per cominciare, allora sei nel business sbagliato. Meno non è più quando si tratta della verbosità del tuo codice!
Ha superato il

39
@Scoperto - Non sono completamente d'accordo. In primo luogo, dove conta la chiarezza in questo esempio? Più vicino al consumatore. Il consumatore chiede esplicitamente INotifyPropertyChanged. Solo perché siamo in grado di creare questo tipo di codice con generazione di micro o macro codice, non significa che sia una buona idea di scriverlo. Quel gunk di INotifyPropertyChanged è semplicemente banale, banale, idraulico e in realtà toglie la leggibilità della classe in questione.
Ben Scheirman,

31
Contrassegnato perché stai confondendo il modello di Iniezione delle dipendenze e il modello di Localizzatore servizi. Il primo è pensato per essere usato al posto del secondo. Ad esempio, un oggetto (o l'intero sistema) non dovrebbe mai chiamare Resolve (...) per ottenere un'istanza di IShippingService. Gli oggetti che necessitano di un IShippingService dovrebbero ricevere un riferimento all'istanza che devono utilizzare quando vengono costruiti e un livello superiore è responsabile della connessione dei due oggetti insieme.
Nat,

30
Qualunque cosa sia (IoC, DynamicProxy, AOP o magia nera ) qualcuno può collegarmi a un articolo concreto / documentazione specifica nel framework che fornisce maggiori dettagli per raggiungere questo obiettivo?
Fostandy,

138

Sono con te, Vadim. I contenitori IoC prendono un concetto semplice, elegante e utile e lo rendono qualcosa che devi studiare per due giorni con un manuale di 200 pagine.

Personalmente sono perplesso su come la comunità IoC abbia preso un bellissimo ed elegante articolo di Martin Fowler e lo abbia trasformato in un mucchio di strutture complesse tipicamente con manuali di 200-300 pagine.

Cerco di non essere giudicante (HAHA!), Ma penso che le persone che usano i contenitori IoC siano (A) molto intelligenti e (B) carenti di empatia per le persone che non sono così intelligenti come sono. Tutto ha perfettamente senso per loro, quindi hanno difficoltà a capire che molti programmatori ordinari troveranno i concetti confusi. È la maledizione della conoscenza . Le persone che comprendono i contenitori IoC hanno difficoltà a credere che ci siano persone che non lo capiscono.

Il vantaggio più prezioso dell'utilizzo di un contenitore IoC è che puoi avere un interruttore di configurazione in un unico posto che ti consente di passare, per esempio, tra la modalità test e la modalità di produzione. Ad esempio, supponiamo di avere due versioni delle classi di accesso al database ... una versione che ha registrato in modo aggressivo e fatto molta convalida, che è stata utilizzata durante lo sviluppo, e un'altra versione senza registrazione o convalida che è stata incredibilmente veloce per la produzione. È bello poter passare da una all'altra in un unico posto. D'altra parte, si tratta di un problema abbastanza banale, facilmente gestibile in modo più semplice senza la complessità dei contenitori IoC.

Credo che se usi i contenitori IoC, il tuo codice diventa, francamente, molto più difficile da leggere. Il numero di posti che devi guardare per capire cosa sta tentando di fare il codice aumenta di almeno uno. E da qualche parte in paradiso un angelo grida.


81
Penso che tu abbia confuso un po 'i tuoi fatti Joel. IoC e container vennero per primi, poi arrivò il trattato di Martin, non viceversa.
Glenn Block,

55
La maggior parte dei contenitori ti consente di iniziare molto rapidamente. Con autofac, mappa della struttura, unità, ninject, ecc. Dovresti riuscire a far funzionare il container in circa 5 minuti. Sì, hanno funzionalità avanzate ma non hai bisogno di quelle per decollare.
Glenn Block,

58
Gioele, pensavo allo stesso modo di te. Poi ho letteralmente e seriamente speso cinque minuti per capire come far funzionare la configurazione di base e sono rimasto sorpreso dalla regola 80/20 in azione. Rende il codice molto più pulito, specialmente nei casi semplici.
Justin Bozonier,

55
Sto programmando da meno di due anni e ho letto il wiki di Ninject e l'ho preso. Da allora uso DI in molti posti. Hai visto che? Meno di due anni e leggendo alcune pagine su una wiki. Non sono un mago o altro. Non mi considero un genio, ma è stato facile per me capire. Non riesco a immaginare qualcuno che capisca davvero OO non essere in grado di afferrarlo. Inoltre, a quali manuali di 200-300 pagine ti riferisci? Non li ho mai visti. Tutti i contenitori IoC che ho usato sono piuttosto corti e puntuali.
JR Garcia,

35
+1 ben scritto e pensato. Una cosa che penso che molte persone non capiscono è che l'aggiunta in un framework o simili per gestire "magicamente" qualcosa aggiunge automaticamente complessità e limitazioni a un sistema. A volte ne vale la pena, ma spesso qualcosa viene trascurato e l'entropia viene rilasciata nel codice cercando di risolvere un problema imposto dalle limitazioni. Potrebbe risparmiare tempo in anticipo, ma le persone devono rendersi conto che può costare 100 volte di più, cercando di risolvere qualche oscuro problema, solo una manciata di persone ha davvero abbastanza conoscenze da risolvere.
kemiller2002,

37

Presumibilmente nessuno ti sta obbligando a utilizzare un framework contenitore DI. Stai già utilizzando DI per disaccoppiare le tue classi e migliorare la testabilità, in modo da ottenere molti dei vantaggi. In breve, stai favorendo la semplicità, che è generalmente una buona cosa.

Se il sistema raggiunge un livello di complessità in cui il DI manuale diventa un lavoro ingrato (ovvero aumenta la manutenzione), confrontarlo con la curva di apprendimento del team di un framework contenitore DI.

Se è necessario un maggiore controllo sulla gestione della durata delle dipendenze (ovvero, se si sente la necessità di implementare il modello Singleton), esaminare i contenitori DI.

Se si utilizza un contenitore DI, utilizzare solo le funzionalità necessarie. Salta il file di configurazione XML e configuralo nel codice se è sufficiente. Attenersi all'iniezione del costruttore. Le basi di Unity o StructureMap possono essere ridotte a un paio di pagine.

C'è un ottimo post sul blog di Mark Seemann su questo: quando usare un contenitore DI


Ottima risposta L'unica cosa su cui differirei nell'opinione è se l'iniezione manuale MAI possa o meno essere considerata meno complessa o meno.
wekempf,

+1. Una delle migliori risposte qui. Grazie anche per il link dell'articolo del blog.
stakx - non contribuisce più il

1
+1 per il punto di vista equilibrato. I contenitori IoC non sono sempre buoni e non sono sempre cattivi. Se non vedi un bisogno, non utilizzarlo. Sappi solo che lo strumento è lì se vedi un bisogno.
Phil

32

A mio avviso, il vantaggio numero uno di un IoC è la capacità di centralizzare la configurazione delle dipendenze.

Se stai usando l'iniezione di dipendenza, il tuo codice potrebbe apparire così

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

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

Se hai usato una classe IoC statica, al contrario di, i file di configurazione IMHO più confusi, potresti avere qualcosa del genere:

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
}

Quindi, la tua classe IoC statica sarebbe simile a questa, sto usando Unity qui.

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

11
Ben, quello di cui stai parlando è davvero la posizione del servizio, non l'iniezione di dipendenza - c'è una differenza. Preferisco sempre il primo al secondo. stevenharman.net/blog/archive/2009/09/25/…
stevenharman

23
Invece di eseguire IoC.Resolve <T> nel costruttore predefinito, dovresti sfruttare la capacità del contenitore IOC di costruire l'oggetto per te. Sbarazzati di quel costruttore vuoto e lascia che il contenitore IOC costruisca l'oggetto per te. var presenter = IoC.Resolve <CustomerPresenter> (); Ecco! tutte le dipendenze già cablate.
Ben Scheirman,

12
L'aspetto negativo della centralizzazione di questo tipo di configurazione è la decontestualizzazione della conoscenza. Joel sottolinea questo problema dicendo: "Il codice diventa, francamente, molto più difficile da leggere".
Scott Bellware,

6
@sbellware, quale conoscenza? L'interfaccia considerata come una dipendenza primaria funge da contratto e dovrebbe essere sufficiente sia per comunicare il suo scopo e comportamento, no? Suppongo che tu possa riferirti alle conoscenze acquisite dallo spelunking nell'attuazione del contratto, nel qual caso avresti bisogno di rintracciare la configurazione appropriata? Mi affido a un computer (R # in VS, IntelliJ, ecc.) Per questo in quanto sono grandiosi nella navigazione di nodi e bordi di un grafico; Riservo lo stack del mio cervello per il contesto del sito di lavoro su cui sono attualmente concentrato.
Stevenharman,

7
Steve, conosco il testo .NET e ho camminato per molti anni. Ho anche un'intima familiarità con altre modalità e forme e capisco visceralmente che ci sono veri e propri compromessi. So come e quando fare questi compromessi, ma la diversità nella mia vita lavorativa mi permette almeno di capire concretamente la prospettiva di Joel. Invece di insegnarmi su strumenti e trucchi che utilizzo da più tempo della maggior parte dei mono-culturisti di .NET, dimmi qualcosa che non conosco. Mi riservo la riserva del mio cervello per il pensiero diverso e critico piuttosto che per la stasi e l'ortodossia di alt.net.
Scott Bellware,

32

I contenitori IoC sono anche utili per caricare dipendenze di classe profondamente nidificate. Ad esempio se avevi il seguente codice usando Depedency Injection.

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

Se tutte queste dipendenze sono state caricate nel contenitore IoC, è possibile risolvere il Servizio clienti e tutte le dipendenze figlio verranno automaticamente risolte.

Per esempio:

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

Ma la classe IoC viene chiamata utilizzando l'anti-pattern del localizzatore di servizi. Il_contenitore dovrebbe essere passato usando il costruttore DI invece di chiamare statico T Resolve <T> ()?
Michael Freidgeim,

@bendewey Il tuo esempio implica che passa automaticamente gli argomenti ai costruttori per il nuovo servizio clienti e il nuovo deposito clienti. Funziona così? E se avessi anche altri parametri aggiuntivi?
Brain2000,

28

Sono un fan della programmazione dichiarativa (guarda a quante domande SQL rispondo), ma i contenitori IoC che ho visto sembrano troppo arcani per il loro bene.

... o forse gli sviluppatori dei contenitori IoC non sono in grado di scrivere una documentazione chiara.

... oppure entrambi sono veri in un modo o nell'altro.

Non penso che il concetto di contenitore IoC sia cattivo. L'implementazione deve essere sufficientemente potente (ovvero flessibile) per essere utile in un'ampia varietà di applicazioni, ma semplice e facilmente comprensibile.

Probabilmente sono sei l'una e mezza dozzina dell'altra. Una vera applicazione (non un giocattolo o una demo) è destinata a essere complessa, tenendo conto di molti casi angolari ed eccezioni alle regole. O incapsuli quella complessità nel codice imperativo, oppure nel codice dichiarativo. Ma devi rappresentarlo da qualche parte.


5
Mi piace la tua osservazione sull'inevitabile presenza di complessità. La differenza tra il cablaggio basato su contenitore e quello manuale di dipendenza è che usando un contenitore, è possibile concentrarsi su ciascun componente individualmente, e quindi gestire la crescente complessità in modo abbastanza lineare. I sistemi con cablaggio di dipendenza manuale sono più difficili da evolvere, poiché l'atto di configurare un componente è difficile da isolare dalla configurazione di tutto il resto.
Nicholas Blumhardt,

Mi piace AspectJ. Non è Java, ma non è neanche XML. È una specie di IS Java, in quanto sembra e sembra Java essendo una sua estensione. Preferirei tenere i miei aspetti fuori dai miei POJO e nella mia logica aziendale. Vorrei quasi tenere fuori anche i miei IoC. C'è del tutto troppo XML negli strumenti di cablaggio, IMHO. Se devo avere file di configurazione, lascia che siano script BeanShell. / Java, applica qui i tuoi workalikes .Net ->.
Chris K,

2
Comprendo certamente la tua risposta, ma penso che gran parte del problema sia che molti dei framework IoC (e, in realtà, molti altri framework anche per altre cose) sono descritti e documentati per altri già molto familiari con il concetti e terminologia richiesti. Sto imparando molto su questo aver appena ereditato il codice di un altro programmatore. Faccio fatica a capire perché il codice utilizza l'IoC quando i "grafici degli oggetti" sono piuttosto piccoli e non esiste una copertura di test reale del codice. Come programmatore SQL, puoi probabilmente apprezzare meglio l'uso di IoC con un ORM.
Kenny Evitt,

1
@KennyEvitt, buon punto, sembrerebbe prematuro usare un framework IoC se uno ha un'app semplice e non si è nemmeno preso la briga di avere una buona copertura dei test.
Bill Karwin,

27

L'uso di un contenitore consiste principalmente nel passare da uno stile imperativo / di scripting di inizializzazione e configurazione a uno dichiarativo . Ciò può avere alcuni effetti benefici diversi:

  • Riduzione delle routine di avvio del programma principale di hairball .
  • Abilitazione delle capacità di riconfigurazione dei tempi di implementazione abbastanza profonde.
  • Fare dello stile iniettabile da dipendenza il percorso di minor resistenza per i nuovi lavori.

Naturalmente, potrebbero esserci difficoltà:

  • Il codice che richiede una complessa gestione di avvio / spegnimento / ciclo di vita potrebbe non essere facilmente adattato a un contenitore.
  • Probabilmente dovrai esplorare qualsiasi problema personale, di processo e di cultura del team, ma è per questo che hai chiesto ...
  • Alcuni toolkit stanno rapidamente diventando loro stessi dei pesi massimi, incoraggiando il tipo di dipendenza profonda che molti container DI hanno iniziato come una reazione negativa.

23

Mi sembra che tu abbia già costruito il tuo contenitore IoC (usando i vari schemi descritti da Martin Fowler) e mi stai chiedendo perché l'implementazione di qualcun altro sia migliore della tua.

Quindi, hai un sacco di codice che funziona già. E mi chiedo perché vorresti sostituirlo con l'implementazione di qualcun altro.

Pro per considerare un contenitore IoC di terze parti

  • Ottieni bug corretti gratuitamente
  • Il design della biblioteca potrebbe essere migliore del tuo
  • Le persone potrebbero avere già familiarità con la particolare libreria
  • La libreria potrebbe essere più veloce della tua
  • Potrebbe avere alcune funzionalità che desideri siano state implementate ma non hai mai avuto il tempo di (hai un localizzatore di servizi?)

Contro

  • Ricevi bug gratuitamente, :)
  • Il design della biblioteca potrebbe essere peggiore del tuo
  • Devi imparare una nuova API
  • Troppe funzioni che non utilizzerai mai
  • Di solito è più difficile eseguire il debug del codice che non hai scritto
  • La migrazione da un precedente contenitore IoC può essere noiosa

Quindi, valuta i tuoi pro contro i tuoi contro e prendi una decisione.


Sono d'accordo con te Sam - DI è un tipo di IoC, quindi se il poster originale sta facendo DI, stanno già facendo IoC, quindi la vera domanda è: perché lanciare il tuo.
Jamie Love,

3
Non sono d'accordo. IOC è un modo di fare DI. I contenitori IOC eseguono il DI assumendo il controllo dell'istanza di tipo utilizzando la riflessione di runtime dinamica per soddisfare e iniettare dipendenze. L'OP sta suggerendo che sta facendo un DI statico e sta valutando il motivo per cui ha bisogno di scambiare i benefici del tempo di compilazione controllando i benefici spesso associati alla riflessione dinamica del runtime del CIO.
Maxm007,

17

Penso che la maggior parte del valore di un IoC sia ottenuto usando DI. Dal momento che lo stai già facendo, il resto del vantaggio è incrementale.

Il valore che otterrai dipenderà dal tipo di applicazione su cui stai lavorando:

  • Per multi-tenant, il contenitore IoC può occuparsi di parte del codice dell'infrastruttura per il caricamento di diverse risorse client. Quando è necessario un componente specifico per il client, utilizzare un selettore personalizzato per gestire la logica e non preoccuparsi del codice client. Puoi certamente costruirlo tu stesso, ma ecco un esempio di come un IoC può aiutare.

  • Con molti punti di estensibilità, l'IoC può essere utilizzato per caricare componenti dalla configurazione. Questa è una cosa comune da costruire ma gli strumenti sono forniti dal contenitore.

  • Se si desidera utilizzare AOP per alcune problematiche trasversali, l' IoC fornisce gli hook per intercettare le invocazioni del metodo. Ciò è meno frequente nei progetti ad hoc, ma l'IoC lo rende più semplice.

Ho scritto funzionalità come questa prima, ma se ho bisogno di una di queste funzionalità ora preferirei utilizzare uno strumento pre-costruito e testato se si adatta alla mia architettura.

Come menzionato da altri, puoi anche configurare centralmente quali classi vuoi usare. Sebbene questa possa essere una buona cosa, ha un costo di indirizzamento errato e complicazioni. I componenti principali per la maggior parte delle applicazioni non vengono sostituiti molto, quindi il compromesso è un po 'più difficile da realizzare.

Uso un contenitore IoC e apprezzo la funzionalità, ma devo ammettere che ho notato il compromesso: il mio codice diventa più chiaro a livello di classe e meno chiaro a livello di applicazione (ovvero visualizzazione del flusso di controllo).


16

Sono un drogato di COI in fase di recupero. Sto trovando difficile giustificare l'uso di IOC per DI nella maggior parte dei casi in questi giorni. I contenitori IOC sacrificano il controllo del tempo di compilazione e presumibilmente in cambio offrono una configurazione "facile", una gestione complessa della durata e la scoperta immediata delle dipendenze in fase di esecuzione. Trovo che la perdita del controllo del tempo di compilazione e le conseguenti magie / eccezioni del tempo di esecuzione, non valgano le campane e i fischi nella stragrande maggioranza dei casi. Nelle grandi applicazioni aziendali possono rendere molto difficile seguire ciò che sta succedendo.

Non compro l'argomento della centralizzazione perché puoi centralizzare facilmente anche l'installazione statica usando una factory astratta per la tua applicazione e rinviando religiosamente la creazione di oggetti alla factory astratta, ovvero eseguendo la DI corretta.

Perché non fare un DI statico privo di magia come questo:

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

La configurazione del contenitore è l'implementazione astratta della fabbrica, le registrazioni sono implementazioni di membri astratti. Se hai bisogno di una nuova dipendenza singleton, aggiungi semplicemente un'altra proprietà astratta alla fabbrica astratta. Se è necessaria una dipendenza temporanea, è sufficiente aggiungere un altro metodo e iniettarlo come Func <>.

vantaggi:

  • Tutta la configurazione e la creazione di oggetti sono centralizzate.
  • La configurazione è solo codice
  • Il controllo del tempo di compilazione semplifica la manutenzione poiché non puoi dimenticare di aggiornare le tue registrazioni.
  • Nessuna magia di riflessione durante il runtime

Consiglio agli scettici di fare un prossimo progetto sul campo verde e onestamente chiediti a che punto hai bisogno del container. È facile includere un fattore in un contenitore IOC in un secondo momento, poiché si sta semplicemente sostituendo un'implementazione di fabbrica con un modulo di configurazione del contenitore IOC.


Questo non mi sembra un argomento contro l'IoC, ma un argomento contro i framework IoC configurabili . Le tue fabbriche qui non si riducono a semplici IoC codificati ?
Mr.Mindor,

Penso che quando il poster menzionava IoC si intendesse un framework di container IoC. Grazie per la scrittura originale questo mi aiuta a convincermi a non avere un framework. Quindi, se è configurabile nei test e nel codice di produzione, qual è il vantaggio di un framework "configurabile"?
Huggie,

Dalla mia comprensione della terminologia, Inversion Of Control significa trovare dipendenze in fase di esecuzione. Quindi sì, potresti dire che la fabbrica è fondamentalmente un contenitore hardcoded o statico, ma non è un contenitore IOC. I tipi vengono risolti al momento della compilazione. È un argomento contro l'utilizzo di framework configurabili che utilizzano le ricerche di dizionario per risolvere i tipi in fase di esecuzione.
Maxm007,

14

Il più grande vantaggio di usare i contenitori IoC per me (personalmente, io uso Ninject) è stato quello di eliminare il passaggio di impostazioni e altri tipi di oggetti di stato globali.

Non programma per il Web, la mia è un'applicazione console e in molti punti in profondità nella struttura ad albero degli oggetti ho bisogno di avere accesso alle impostazioni o ai metadati specificati dall'utente che vengono creati su un ramo completamente separato della struttura ad albero degli oggetti. Con IoC dico semplicemente a Ninject di trattare le Impostazioni come un singleton (perché ce ne sono sempre solo un'istanza), richiedo le Impostazioni o il Dizionario nel costruttore e presto ... appaiono magicamente quando ne ho bisogno!

Senza usare un contenitore IoC avrei dovuto passare le impostazioni e / o i metadati attraverso 2, 3, ..., n oggetti prima che fosse effettivamente utilizzato dall'oggetto che ne aveva bisogno.

Ci sono molti altri vantaggi per i contenitori DI / IoC come altre persone hanno dettagliato qui e passare dall'idea di creare oggetti a richiedere oggetti può essere strabiliante, ma l'uso di DI è stato molto utile per me e il mio team, quindi forse puoi aggiungerlo al tuo arsenale!


2
Questo è un grande vantaggio. Non riesco a credere che nessun altro lo abbia mai menzionato prima. Senza la necessità di passare o memorizzare oggetti temporali rende il codice molto più pulito.
Finglas

1
Gli oggetti globali di @qble funzionano alla grande ... se vuoi che la tua base di codice sia un incubo strettamente accoppiato e non testabile che diventa sempre più difficile da modificare nel tempo. buona fortuna.
Jeffrey Cameron,

2
@JeffreyCameron Il contenitore IoC è un oggetto globale. Non rimuove le tue dipendenze globali.
qble

3
È vero, tuttavia il contenitore IoC è trasparente per il resto dell'applicazione. Lo chiami una volta all'avvio per ottenere un'istanza e questa è l'ultima che vedi. Con oggetti globali il tuo codice è pieno di forti dipendenze
Jeffrey Cameron,

1
@qble usa le fabbriche per creare istanze transitorie al volo. Le fabbriche sono servizi singleton. Se le istanze transitorie necessitano di qualcosa dal contenitore IOC, collegarle alla fabbrica e fare in modo che la fabbrica passi la dipendenza nell'istanza transitoria.
Jeffrey Cameron,

14

I framework IoC sono eccellenti se si desidera ...

  • ... buttare via la sicurezza del tipo. Molti (tutti?) Framework IoC ti costringono ad eseguire il codice se vuoi essere certo che tutto sia collegato correttamente. "Ehi! Spero di aver impostato tutto in modo che le mie inizializzazioni di queste 100 classi non falliscano nella produzione, generando eccezioni senza puntatore!"

  • ... lettiera il codice con variabili globali (quadri IoC sono tutti di cambiare stati globali).

  • ... scrivere codice scadente con dipendenze poco chiare che è difficile da riformattare poiché non si saprà mai cosa dipende da cosa.

Il problema con IoC è che le persone che li usano erano solite scrivere codice in questo modo

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

che è ovviamente imperfetto poiché la dipendenza tra Foo e Bar è cablata. Poi hanno capito che sarebbe stato meglio scrivere codice come

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

che è anche difettoso, ma meno ovviamente. In Haskell il tipo di Foo()sarebbe, IO Fooma in realtà non vuoi la IO-part ed è un segnale di avvertimento che qualcosa non va nel tuo design se lo hai ottenuto.

Per sbarazzartene (la parte IO), ottieni tutti i vantaggi dei framework IoC e nessuno dei suoi inconvenienti potresti invece usare una fabbrica astratta.

La soluzione corretta sarebbe qualcosa di simile

data Foo = Foo { apa :: Bar }

o forse

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

e iniettare (ma non lo definirei iniettare) Barra.

Inoltre: guarda questo video con Erik Meijer (inventore di LINQ) in cui afferma che DI è per le persone che non conoscono la matematica (e non potrei essere più d'accordo): http://www.youtube.com/watch?v = 8Mttjyf-8P4

A differenza del signor Spolsky, non credo che le persone che usano i framework IoC siano molto intelligenti: credo semplicemente che non conoscano la matematica.


9
buttare via la sicurezza dei tipi - tranne per il fatto che il tuo esempio è ancora sicuro dei tipi, questa è l'interfaccia. Non stai passando intorno a tipi di oggetti, ma al tipo di interfaccia. E la sicurezza del tipo non elimina comunque le eccezioni di riferimento null, puoi tranquillamente passare riferimenti null come parametri di tipo sicuro - non è affatto un problema di sicurezza del tipo.
Blowdart,

Getti via la sicurezza del tipo poiché non sai se il tipo è cablato o meno nel contenitore IoC (il tipo di <IBar> IoC non IBarlo è, è in effetti IO IBar) .. E sì, nell'esempio di Haskell Non riesco a ottenere un riferimento null.
finnsson,

Digitare la sicurezza non riguarda in realtà un oggetto che viene cablato, almeno non in Java / C #, ma semplicemente l'oggetto del tipo giusto. E ConcreteClass myClass = null è ancora del tipo giusto. Se si desidera imporre non null, entrano in gioco i contratti di codice.
Blowdart,

2
Sono d'accordo con il tuo primo punto in una certa misura, vorrei una spiegazione del 2 ° e del 3 ° non è mai stato un problema per me. L'esempio C # utilizza il contenitore IoC come localizzatore di servizi, un errore comune. E confrontando C # vs Haskell = mele vs arance.
Mauricio Scheffer,

2
Non buttare via la sicurezza del tipo. Alcuni container DI (se non la maggior parte) consentono di verificare il grafico completo dell'oggetto dopo il processo di registrazione. In questo modo è possibile impedire che l'applicazione si avvii in caso di configurazione errata (fail fast) e le mie applicazioni hanno sempre alcuni test unitari che chiamano la configurazione di produzione per permettermi di trovare questi errori durante il test. Consiglio a tutti coloro che usano un contenitore IoC di scrivere alcuni test unitari per assicurarsi che tutti gli oggetti di livello superiore possano essere risolti.
Steven,

10

Ho scoperto che l'implementazione corretta di Dependency Injection tende a costringere i programmatori a utilizzare una varietà di altre pratiche di programmazione che aiutano a migliorare la testabilità, la flessibilità, la manutenibilità e la scalabilità del codice: pratiche come il principio di responsabilità singola, le separazioni delle preoccupazioni e la codifica contro le API. Mi sembra di essere costretto a scrivere classi e metodi più modulari e di dimensioni ridotte, il che semplifica la lettura del codice, perché può essere preso in blocchi di dimensioni ridotte.

Ma tende anche a creare alberi di dipendenza piuttosto grandi, che sono gestiti molto più facilmente tramite un framework (specialmente se si usano le convenzioni) che a mano. Oggi volevo testare qualcosa molto rapidamente in LINQPad, e ho pensato che sarebbe stato troppo fastidioso creare un kernel e caricarlo nei miei moduli, e ho finito per scrivere questo a mano:

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

In retrospettiva, sarebbe stato più veloce utilizzare il framework IoC, poiché i moduli definiscono praticamente tutto questo roba per convenzione.

Dopo aver trascorso un po 'di tempo a studiare le risposte e i commenti su questa domanda, sono convinto che le persone che si oppongono all'utilizzo di un contenitore IoC non stiano praticando una vera iniezione di dipendenza. Gli esempi che ho visto sono di pratiche che sono comunemente confuse con l'iniezione di dipendenza. Alcune persone si lamentano della difficoltà di "leggere" il codice. Se eseguito correttamente, la stragrande maggioranza del codice dovrebbe essere identica quando si utilizza DI a mano come quando si utilizza un contenitore IoC. La differenza dovrebbe risiedere interamente in alcuni "punti di lancio" all'interno dell'applicazione.

In altre parole, se non ti piacciono i contenitori IoC, probabilmente non stai facendo l'iniezione di dipendenza nel modo in cui dovrebbe essere fatto.

Un altro punto: l'iniezione di dipendenza non può davvero essere eseguita a mano se si utilizza la riflessione ovunque. Mentre odio ciò che fa la riflessione sulla navigazione del codice, devi riconoscere che ci sono alcune aree in cui non è possibile evitarlo. ASP.NET MVC, ad esempio, tenta di creare un'istanza del controller tramite la riflessione su ogni richiesta. Per eseguire manualmente l'iniezione di dipendenza, è necessario rendere ogni controller una "root di contesto", in questo modo:

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

Ora confronta questo con consentire a un framework DI di farlo per te:

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

Utilizzando un framework DI, notare che:

  • Posso testare l'unità di questa classe. Creando un finto ISimpleWorkflowInstanceMerger, posso provare che viene utilizzato nel modo che prevedo, senza la necessità di una connessione al database o altro.
  • Uso molto meno codice e il codice è molto più facile da leggere.
  • Se una delle dipendenze della mia dipendenza cambia, non devo apportare modifiche al controller. Ciò è particolarmente utile se si considera che è probabile che più controller utilizzino alcune delle stesse dipendenze.
  • Non faccio mai esplicitamente riferimento alle classi dal mio livello dati. La mia applicazione web può semplicemente includere un riferimento al progetto contenente l' ISimpleWorkflowInstanceMergerinterfaccia. Ciò mi consente di suddividere l'applicazione in moduli separati e di mantenere una vera architettura multilivello, che a sua volta rende le cose molto più flessibili.

Un'applicazione Web tipica avrà un bel numero di controller. Tutto il dolore di fare DI manualmente a mano in ogni controller aumenterà davvero man mano che la tua applicazione cresce. Se hai un'applicazione con una sola root di contesto, che non tenta mai di creare un'istanza di un servizio per riflessione, questo non è un problema così grave. Tuttavia, qualsiasi applicazione che utilizza Iniezione dipendenze diventerà estremamente costosa da gestire una volta raggiunta una certa dimensione, a meno che non si utilizzi un framework di qualche tipo per gestire il grafico delle dipendenze.


9

Ogni volta che usi la "nuova" parola chiave, stai creando una dipendenza di classe concreta e un campanello d'allarme dovrebbe suonare nella tua testa. Diventa più difficile testare questo oggetto da solo. La soluzione è programmare le interfacce e iniettare la dipendenza in modo che l'oggetto possa essere testato unitamente a tutto ciò che implementa quell'interfaccia (es. Mock).

Il problema è che devi costruire oggetti da qualche parte. Un modello Factory è un modo per spostare l'accoppiamento dai POXO (Oggetti semplici "Inserisci qui il tuo linguaggio OO"). Se tu e i tuoi colleghi scrivete tutti codice in questo modo, un contenitore IoC è il prossimo "Miglioramento incrementale" che potete apportare alla vostra base di codice. Sposterà tutto quel brutto codice Factoryplate dalla fabbrica dai tuoi oggetti puliti e dalla logica aziendale. Lo prenderanno e lo adoreranno. Cavolo, fai un discorso aziendale sul perché lo ami e fai entusiasmare tutti.

Se i tuoi colleghi non stanno ancora facendo un DI, allora ti suggerirei di concentrarti prima su quello. Spargi la voce su come scrivere codice pulito facilmente testabile. Il codice DI pulito è la parte difficile, una volta che sei lì, spostare la logica di cablaggio degli oggetti dalle classi Factory a un contenitore IoC dovrebbe essere relativamente banale.


8

Poiché tutte le dipendenze sono chiaramente visibili, promuove la creazione di componenti che sono liberamente accoppiati e allo stesso tempo facilmente accessibili e riutilizzabili in tutta l'applicazione.


7

Non hai bisogno di un contenitore IoC.

Ma se stai seguendo rigorosamente un modello DI, scoprirai che averne uno rimuoverà una tonnellata di codice ridondante e noioso.

Questo è spesso il momento migliore per usare una libreria / framework, quando capisci cosa sta facendo e potresti farlo senza la libreria.


6

Mi capita proprio di essere in procinto di strappare il codice DI cresciuto in casa e sostituirlo con un CIO. Probabilmente ho rimosso ben oltre 200 righe di codice e sostituito con circa 10. Sì, ho dovuto fare un po 'di apprendimento su come utilizzare il contenitore (Winsor), ma sono un ingegnere che lavora su tecnologie Internet nel 21 ° secolo, quindi ci sono abituato. Probabilmente ho trascorso circa 20 minuti a guardare come. Ne è valsa la pena.


3
"ma sono un ingegnere che lavora sulle tecnologie Internet nel 21 ° secolo, quindi sono abituato a farlo" -> +1
user96403

5

Mentre continui a disaccoppiare le tue classi e invertire le tue dipendenze, le classi continuano a rimanere piccole e il "grafico delle dipendenze" continua a crescere di dimensioni. (Questo non è male.) L'uso delle funzionalità di base di un contenitore IoC rende banale il cablaggio di tutti questi oggetti, ma farlo manualmente può diventare molto oneroso. Ad esempio, se volessi creare una nuova istanza di "Foo" ma ha bisogno di una "barra". E un "Bar" ha bisogno di "A", "B" e "C". E ognuna di queste ha bisogno di altre 3 cose, ecc. Ecc. (Sì, non riesco a trovare buoni nomi falsi :)).

L'uso di un contenitore IoC per creare il grafico degli oggetti per te riduce la complessità di una tonnellata e lo espelle in una configurazione singola. Dico semplicemente "creami un 'Foo'" e capisco cosa serve per costruirne uno.

Alcune persone usano i contenitori IoC per molte più infrastrutture, il che va bene per scenari avanzati ma in quei casi sono d'accordo che può offuscare e rendere il codice difficile da leggere e il debug per i nuovi sviluppatori.


1
perché continuiamo a placare il minimo comune denominatore piuttosto che lavorare per tirarli su?
Stevenharman,

4
Non ho detto nulla sul placare. Ho appena detto che gli scenari avanzati possono rendere le cose più offuscate per i nuovi sviluppatori - questo è un dato di fatto. Non ho detto "quindi non farlo". Ma anche allora (tempo per l'avvocato del diavolo) ci sono spesso altri modi per realizzare la stessa cosa che rendono una base di codice più esplicita e accessibile. Nascondere la complessità nella configurazione personalizzata del proprio strumento di infrastruttura solo perché è impossibile non è la ragione giusta e non fa salire nessuno.
Aaron Lerch,

4

Dittos su Unity. Diventa troppo grande e puoi sentire lo scricchiolio nelle travi.

Non mi sorprende mai quando la gente inizia a parlare di come appare pulito il codice IoC sono gli stessi tipi di persone che un tempo parlavano di come i modelli in C ++ fossero il modo elegante per tornare indietro negli anni '90, ma oggigiorno li considereranno arcani . Bah!


2

Nel mondo .NET AOP non è troppo popolare, quindi per DI un framework è la tua unica vera opzione, sia che tu ne scriva uno tu stesso o usi un altro framework.

Se hai usato AOP puoi iniettare quando compili la tua applicazione, che è più comune in Java.

I vantaggi di DI sono molti, come l'accoppiamento ridotto, quindi i test dell'unità sono più facili, ma come lo implementerai? Vuoi usare la riflessione per farlo da solo?


Il nuovo framework MEF consente proprio questo per .NET e devo dire che il codice è più pulito e più facile da capire, rende molto più semplice la comprensione del concetto DI.
terjetyl,

4
@TT: MEF non è un contenitore di iniezione di dipendenza e non ha nulla a che fare con AOP.
Mauricio Scheffer,

2

Sono passati quasi 3 anni, eh?

  1. Il 50% di coloro che hanno commentato a favore dei framework IoC non capisce la differenza tra i frame IoC e IoC. Dubito che sappiano che puoi scrivere codice senza essere distribuito su un server di app

  2. Se prendiamo il più popolare framework Java Spring, è la configurazione IoC spostata da XMl nel codice e ora appare così

`@Configuration AppConfig di classe pubblica {

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

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

} `E abbiamo bisogno di un framework per fare questo, perché esattamente?


1

Onestamente non trovo che ci siano molti casi in cui sono necessari contenitori IoC e, il più delle volte, aggiungono solo complessità non necessaria.

Se lo stai usando solo per semplificare la costruzione di un oggetto, dovrei chiederti, stai istanziando questo oggetto in più di una posizione? Un singleton non soddisfa le tue esigenze? Stai cambiando la configurazione in fase di esecuzione? (Cambio dei tipi di origine dati, ecc.).

In caso affermativo, potrebbe essere necessario un contenitore IoC. In caso contrario, stai semplicemente spostando l'inizializzazione da dove lo sviluppatore può facilmente vederla.

Chi ha detto che un'interfaccia è comunque meglio dell'eredità? Supponi di provare un servizio. Perché non usare il costruttore DI e creare beffe delle dipendenze usando l'ereditarietà? La maggior parte dei servizi che utilizzo hanno solo alcune dipendenze. L'esecuzione di test unitari in questo modo impedisce di conservare un sacco di interfacce inutili e significa che non è necessario utilizzare Resharper per trovare rapidamente la dichiarazione di un metodo.

Credo che per la maggior parte delle implementazioni, dire che i contenitori IoC rimuovono il codice non necessario è un mito.

Innanzitutto, è necessario installare il contenitore. Quindi devi ancora definire ogni oggetto che deve essere inizializzato. Quindi non si salva il codice durante l'inizializzazione, lo si sposta (a meno che l'oggetto non venga utilizzato più di una volta. È meglio come Singleton?). Quindi, per ogni oggetto che hai inizializzato in questo modo, devi creare e mantenere un'interfaccia.

Qualcuno ha qualche idea su questo?


Anche per i piccoli progetti, mettere la configurazione in XML lo rende molto più pulito e modulare - e per la prima volta, rende il codice riutilizzabile (poiché non è più contaminato dalla colla). Per prodotti software adeguati, è possibile configurare o scambiare ad esempio ShippingCostCalculatoro ProductUIPresentation, o qualsiasi altra strategia / implementazione, all'interno della stessa base di codice esatta , distribuendo solo un file XML modificato.
Thomas W,

Se è implementato volenti o nolenti, come pratica generale, penso che tu aggiunga più complessità, non meno. Quando devi scambiare qualcosa in fase di esecuzione, sono fantastici, ma perché aggiungere l'offuscamento? Perché mantenere il sovraccarico delle interfacce extra per cose che non verranno mai disattivate? Non sto dicendo di non usare l'IoC per i test. Sicuramente, usa l'iniezione di proprietà o persino di dipendenza del costruttore. Devi usare un'interfaccia? Perché non eseguire la sottoclasse degli oggetti di test? Non sarebbe più pulito?
Fred,

Non hai bisogno di interfacce per eseguire la configurazione XML / IoC. Ho trovato grandi miglioramenti nella chiarezza in un piccolo progetto con solo ~ 8 pagine e 5 o 6 servizi. C'è meno offuscamento poiché i servizi e i controller non hanno più bisogno del codice colla.
Thomas W,

1

Avresti bisogno di un contenitore IoC se avessi bisogno di centralizzare la configurazione delle tue dipendenze in modo che possano essere facilmente scambiate in massa. Questo ha più senso nel TDD, dove molte dipendenze vengono scambiate, ma dove c'è poca interdipendenza tra le dipendenze. Ciò avviene al costo di offuscare il flusso di controllo della costruzione di oggetti in una certa misura, quindi è importante avere una configurazione ben organizzata e ragionevolmente documentata. È anche bene avere una ragione per farlo, altrimenti è una semplice astrazione placcatura in oro . L'ho visto fare così male che è stato trascinato per essere l'equivalente di un'istruzione goto per i costruttori.


1

Ecco perché. Il progetto si chiama IOC-with-Ninject. Puoi scaricarlo ed eseguirlo con Visual Studio. Questo esempio usa Ninject ma TUTTE le "nuove" istruzioni si trovano in una posizione e puoi cambiare completamente il modo in cui l'applicazione viene eseguita cambiando il modulo di bind da utilizzare. L'esempio è impostato in modo da poterti associare a una versione derisa dei servizi o alla versione reale. Nei piccoli progetti che potrebbero non avere importanza, ma nei grandi progetti è un grosso problema.

Giusto per essere chiari, i vantaggi come li vedo io: 1) TUTTE le nuove dichiarazioni in una posizione alla radice del codice. 2) Ricodificare totalmente il codice con una modifica. 3) Punti extra per "fattore cool" perché è ... beh: figo. : p


1

Cercherò di scoprire perché il CIO potrebbe non essere buono dal mio punto di vista.

Come per tutto il resto, il contenitore IOC (o come direbbe Einstein I = OC ^ 2) è un concetto che devi decidere tu stesso se ne hai bisogno o meno nel tuo codice. La recente protesta della moda per il CIO è solo questo, la moda. Non innamorarti della moda, questo è il primo. Ci sono miriadi di concetti là fuori che potresti implementare nel tuo codice. Prima di tutto, sto usando l'iniezione di dipendenza da quando ho iniziato a programmare e ho imparato il termine stesso quando è stato reso popolare con quel nome. Il controllo delle dipendenze è un argomento molto vecchio ed è stato affrontato finora in migliaia di miliardi di modi, a seconda di cosa si stava disaccoppiando da cosa. Il disaccoppiamento di tutto da tutto è una sciocchezza. Il problema con il contenitore IOC è che cerca di essere utile come Entity Framework o NHibernate. Mentre scrivere un mappatore relazionale ad oggetto è semplicemente un must non appena si deve accoppiare qualsiasi database con il proprio sistema, il contenitore IOC non è sempre necessario. Quindi, quando il contenitore IOC è utile:

  1. Quando hai una situazione con molte dipendenze che vuoi organizzare
  2. Quando non ti interessa accoppiare il codice con prodotti di terze parti
  3. Quando i tuoi sviluppatori vogliono imparare a lavorare con un nuovo strumento

1: Non è così frequente che tu abbia così tante dipendenze nel tuo codice o che tu ne sia consapevole all'inizio del progetto. Il pensiero astratto è utile quando è necessario il pensiero astratto.

2: L'accoppiamento del codice con codice di terze parti è un problema di HuGe. Stavo lavorando con un codice che ha più di 10 anni e che seguiva a quel tempo concetti sofisticati e avanzati ATL, COM, COM + e così via. Non c'è niente che tu possa fare con quel codice ora. Quello che sto dicendo è che un concetto avanzato offre un vantaggio apparente, ma questo viene annullato a lungo termine con lo stesso vantaggio obsoleto. Aveva appena reso tutto più costoso.

3: Lo sviluppo del software è abbastanza difficile. Puoi estenderlo a livelli irriconoscibili se consenti ad alcuni concetti avanzati di ritagliarsi nel tuo codice. Si è verificato un problema con IOC2. Sebbene stia disaccoppiando le dipendenze, sta anche disaccoppiando il flusso logico. Immagina di aver trovato un bug e di dover fare una pausa per esaminare la situazione. IOC2, come qualsiasi altro concetto avanzato, lo sta rendendo più difficile. Risolvere un bug all'interno di un concetto è più difficile che correggere un bug in un codice più semplice, perché quando si corregge un bug è necessario obbedire nuovamente a un concetto. (Solo per darvi un esempio, C ++ .NET modifica costantemente la sintassi così tanto che è necessario riflettere attentamente prima di refactificare una versione precedente di .NET.) Qual è il problema con IOC? Il problema è nella risoluzione delle dipendenze. La logica per la risoluzione è comunemente nascosta nello stesso IOC2, scritto forse in modo insolito che devi imparare e mantenere. Il tuo prodotto di terze parti sarà presente tra 5 anni? Microsoft non lo era.

La sindrome "Sappiamo come" è scritta dappertutto per quanto riguarda IOC2. Questo è simile ai test di automazione. Fantasioso termine e soluzione perfetta a prima vista, devi semplicemente eseguire tutti i test per eseguire tutta la notte e vedere i risultati al mattino. È davvero doloroso spiegare a società dopo società cosa significano realmente i test automatizzati. I test automatici non sono sicuramente un modo rapido per ridurre il numero di bug che è possibile introdurre durante la notte per aumentare la qualità del prodotto. Ma la moda sta rendendo questa nozione fastidiosamente dominante. IOC2 soffre della stessa sindrome. Si ritiene che sia necessario implementarlo affinché il software sia valido. Ogni recente intervista mi è stato chiesto se sto implementando IOC2 e automazione. Questo è un segno di moda: la società aveva una parte del codice scritto in MFC che non abbandonerà.

Devi imparare IOC2 come qualsiasi altro concetto nel software. La decisione sulla necessità di utilizzare IOC2 è all'interno del team e dell'azienda. Tuttavia, almeno TUTTI i suddetti argomenti devono essere menzionati prima che la decisione venga presa. Solo se vedi che il lato positivo supera il lato negativo, puoi prendere una decisione positiva.

Non c'è nulla di sbagliato in IOC2 tranne che risolve solo i problemi che risolve e introduce i problemi che introduce. Nient'altro. Tuttavia, andare contro la moda è molto difficile, hanno la bocca sudata, i seguaci di tutto. È strano come nessuno di loro sia lì quando il problema con la loro fantasia diventa evidente. Molti concetti nell'industria del software sono stati difesi perché creano profitti, sono scritti libri, conferenze tenute, nuovi prodotti realizzati. Questa è la moda, di solito di breve durata. Non appena le persone trovano qualcos'altro, lo abbandonano completamente. IOC2 è utile ma mostra gli stessi segni di molti altri concetti scomparsi che ho visto. Non so se sopravviverà. Non esiste una regola per questo. Pensi che se è utile, sopravviverà. No, non va così. Una grande azienda ricca è sufficiente e il concetto può morire in poche settimane. Vedremo. NHibernate è sopravvissuto, EF è arrivato secondo. Forse sopravviverà anche IOC2. Non dimenticare che la maggior parte dei concetti nello sviluppo di software non riguarda nulla di speciale, sono molto logici, semplici ed evidenti, e talvolta è più difficile ricordare l'attuale convenzione di denominazione che comprendere il concetto stesso. La conoscenza di IOC2 rende uno sviluppatore uno sviluppatore migliore? No, perché se uno sviluppatore non è stato in grado di elaborare un concetto simile in natura a IOC2, allora sarà difficile per lui o lei capire quale problema IOC2 sta risolvendo, usarlo sembrerà artificiale e potrebbe iniziare a usarlo per essere una specie di politicamente corretto. Non dimenticare che la maggior parte dei concetti nello sviluppo di software non riguarda nulla di speciale, sono molto logici, semplici ed evidenti, e talvolta è più difficile ricordare l'attuale convenzione di denominazione che comprendere il concetto stesso. La conoscenza di IOC2 rende uno sviluppatore uno sviluppatore migliore? No, perché se uno sviluppatore non è stato in grado di elaborare un concetto simile in natura a IOC2, allora sarà difficile per lui o lei capire quale problema IOC2 sta risolvendo, usarlo sembrerà artificiale e potrebbe iniziare a usarlo per essere una specie di politicamente corretto. Non dimenticare che la maggior parte dei concetti nello sviluppo di software non riguarda nulla di speciale, sono molto logici, semplici ed evidenti, e talvolta è più difficile ricordare l'attuale convenzione di denominazione che comprendere il concetto stesso. La conoscenza di IOC2 rende uno sviluppatore uno sviluppatore migliore? No, perché se uno sviluppatore non è stato in grado di elaborare un concetto simile in natura a IOC2, allora sarà difficile per lui o lei capire quale problema IOC2 sta risolvendo, usarlo sembrerà artificiale e potrebbe iniziare a usarlo per essere una specie di politicamente corretto. La conoscenza di IOC2 rende uno sviluppatore uno sviluppatore migliore? No, perché se uno sviluppatore non è stato in grado di elaborare un concetto simile in natura a IOC2, allora sarà difficile per lui o lei capire quale problema IOC2 sta risolvendo, usarlo sembrerà artificiale e potrebbe iniziare a usarlo per essere una specie di politicamente corretto. La conoscenza di IOC2 rende uno sviluppatore uno sviluppatore migliore? No, perché se uno sviluppatore non è stato in grado di elaborare un concetto simile in natura a IOC2, allora sarà difficile per lui o lei capire quale problema IOC2 sta risolvendo, usarlo sembrerà artificiale e potrebbe iniziare a usarlo per essere una specie di politicamente corretto.


0

Personalmente, utilizzo l'IoC come una sorta di mappa strutturale della mia applicazione (Sì, preferisco anche StructureMap;)). Rende facile sostituire le mie solite implementazioni di interfaccia con le implementazioni Moq durante i test. Creare un'impostazione di prova può essere facile come effettuare una nuova chiamata di avvio al mio framework IoC, sostituendo qualsiasi classe sia il mio limite di prova con una simulazione.

Questo probabilmente non è ciò per cui l'IoC è lì, ma è quello per cui mi trovo ad usarlo di più.




0

Mi rendo conto che questo è un post piuttosto vecchio, ma sembra essere ancora ragionevolmente attivo e ho pensato di contribuire con un paio di punti che non sono stati ancora menzionati in altre risposte.

Dirò che sono d'accordo con i benefici dell'iniezione di dipendenza, ma preferisco costruire e gestire gli oggetti da solo, usando uno schema non dissimile da quello delineato da Maxm007 nella sua risposta. Ho riscontrato due problemi principali con l'utilizzo di contenitori di terze parti:

1) Avere una libreria di terze parti che gestisca la vita dei tuoi oggetti "automagicamente" può prestarsi a risultati inaspettati. Abbiamo scoperto che, soprattutto nei progetti di grandi dimensioni, è possibile avere molte più copie di un oggetto di quanto ci si aspetti e più di quanto si farebbe se si gestisse manualmente i cicli di vita. Sono sicuro che questo varia a seconda del framework utilizzato, ma il problema esiste comunque. Questo può anche essere problematico se il tuo oggetto contiene risorse, connessioni dati, ecc., Poiché a volte l'oggetto può vivere più a lungo del previsto. Quindi, inevitabilmente, i contenitori IoC tendono ad aumentare l'utilizzo delle risorse e l'impronta di memoria di un'applicazione.

2) I contenitori IoC, a mio avviso, sono una forma di "programmazione della scatola nera". Ho scoperto che, in particolare, i nostri sviluppatori meno esperti tendono ad abusarne. Permette al programmatore di non dover pensare a come gli oggetti dovrebbero relazionarsi tra loro o come separarli, perché fornisce loro un meccanismo in cui possono semplicemente prendere qualsiasi oggetto che vogliono dal nulla. Ad esempio, potrebbe esserci una buona ragione di progettazione che ObjectA non dovrebbe mai conoscere direttamente ObjectB, ma piuttosto che creare una factory o un bridge o un localizzatore di servizi, un programmatore inesperto dirà semplicemente "nessun problema, prenderò ObjectB dal contenitore IoC ". Ciò può effettivamente portare a un maggiore accoppiamento degli oggetti, che è ciò che l'IoC dovrebbe aiutare a prevenire.


-8

L'iniezione di dipendenza in un progetto ASP.NET può essere eseguita con poche righe di codice. Suppongo che ci sia qualche vantaggio nell'utilizzare un contenitore quando si dispone di un'app che utilizza più front-end e necessita di unit test.


1
Puoi fare un esempio? In che modo DI è diverso in ASP.NET rispetto a qualsiasi altro tipo di applicazione?
tofi9,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.