2010-11-09 33 views
6

Quando si utilizza l'iniezione di dipendenza quali dipendenze si inietta?Quali dipendenze dovrei iniettare?

ho precedentemente iniettato tutte le dipendenze ma hanno trovato quando fa TDD ci sono tipicamente due tipi di dipendenza:

  • quelle che sono veri dipendenze esterne che possono variare per esempio ProductRepository
  • Quelle che esistono puramente per testabilità, ad es. Parte del comportamento della classe che è stato estratto e iniettato solo per testability

Un approccio è quello di iniettare tutte le dipendenze come questo

public ClassWithExternalDependency(IExternalDependency external, 
    IExtractedForTestabilityDependency internal) 
{ 
    // assign dependencies ... 
} 

ma ho trovato questo può causare dipendenza gonfiare nel Registro DI.

Un altro approccio è quello di nascondere il "verificabilità delle dipendenze" come questo

public ClassWithExternalDependency(IExternalDependency external) 
    : this (external, new ConcreteClassOfInternalDependency()) 
{} 

internal ClassWithExternalDependency(IExternalDependency external, 
    IExtractedForTestabilityDependency internal) 
{ 
    // assign dependencies ... 
} 

Si tratta di uno sforzo maggiore, ma sembra avere molto più senso. Lo svantaggio di non tutti gli oggetti sono configurati nel framework DI, rompendo così una "best practice" che ho sentito.

Quale approccio sosterreste e perché?

risposta

1

Credo che tu stia meglio iniettando tutte le tue dipendenze. Se inizia a diventare un po 'ingombrante, probabilmente è un'indicazione che è necessario semplificare un po' le cose o spostare le dipendenze in un altro oggetto. Sentire il "dolore" del tuo design mentre vai può essere davvero illuminante.

Per quanto riguarda la dipendenza eccessiva nel Registro di sistema, si potrebbe considerare l'utilizzo di una sorta di tecnica di rilegatura convenzionale, piuttosto che registrare ogni dipendenza a mano. Alcuni contenitori IoC hanno associazioni di scansione del tipo basate su convenzione incorporate. Ad esempio, ecco una parte di un modulo che uso in un'applicazione Caliburn WPF che utilizza Ninject:

public class AppModule : NinjectModule 
{ 
    public override void Load() 
    { 
     Bind<IShellPresenter>().To<ShellPresenter>().InSingletonScope(); 

     BindAllResults(); 
     BindAllPresenters(); 
    } 

    /// <summary> 
    /// Automatically bind all presenters that haven't already been manually bound 
    /// </summary> 
    public void BindAllPresenters() 
    { 
     Type[] types = Assembly.GetExecutingAssembly().GetTypes(); 

     IEnumerable<Type> presenterImplementors = 
      from t in types 
      where !t.IsInterface 
      && t.Name.EndsWith("Presenter") 
      select t; 

      presenterImplementors.Run(
       implementationType => 
        { 
         if (!Kernel.GetBindings(implementationType).Any()) 
          Bind(implementationType).ToSelf(); 
        }); 
    } 

Anche se ho decine di risultati e presentatori in giro, non ho registrarli in modo esplicito.

0

Di sicuro non inietterò tutte le dipendenze, perché dovevano fermarsi? Vuoi iniettare le tue dipendenze dallo string? Inverto solo le dipendenze di cui ho bisogno per il test dell'unità. Voglio stub il mio database (vedi this example per esempio). Voglio bloccare l'invio di messaggi di posta elettronica. Voglio spegnere l'orologio di sistema. Voglio smettere di scrivere sul file system.

Il problema di invertire tutte le dipendenze possibili, anche quelle che non sono necessarie per i test, è che rendono il testing delle unità molto più difficile e più si stuba e meno si verifica davvero come il sistema agisce davvero . Questo rende i tuoi test molto meno affidabili. Inoltre, complica la tua configurazione DI nella root dell'applicazione.

+1

Uno dei vantaggi di invertire le dipendenze È essere in grado di testare le unità isolatamente. Posso estrarre una classe calcolatrice per testarla isolatamente ma forse non ha bisogno di iniettare (vedi secondo esempio di codice). Non ho potuto testarlo in-situ in quanto avrebbe gonfiato i test per il client della classe calcolatrice. – Alex

+1

Iniettare tutti gli iniettabili, i nuovi tutti i nuovi. Senza questa distinzione, DI richiederebbe tutti gli oggetti che vivono per tutta la durata del programma. Le novità, ovvero i tipi di valore, rappresentano i dati inerti e, facoltativamente, alcuni comportamenti trasformativi associati (ovvero metodi che accettano valori e restituiscono nuovi valori). Gli iniettabili, ovvero i tipi di servizio/business, rappresentano la funzionalità e, facoltativamente, alcuni stati associati al programma. Probabilmente, abbiamo anche i tipi di I/O per rappresentare lo stato esterno, ad es. File(). I tipi di I/O devono essere nuovi, ma dovrebbero essere creati attraverso fabbriche astratte in modo da poter essere derisi per il test. – Jegschemesch

0

Vorrei collegare a mano tutte le mie dipendenze non esterne e "registrare" solo le dipendenze esterne. Quando dico non-esterno, intendo gli oggetti che appartengono al mio componente e che sono stati estrapolati alle interfacce solo per motivi di responsabilità singola/testabilità, non avrei mai avuto altre implementazioni di tali interfacce. Le dipendenze esterne sono cose come connessioni DB, servizi web, interfacce che non appartengono al mio componente.Li registrerei come interfacce perché le loro implementazioni possono essere convertite in test di integrazione per il test di integrazione. Avere un piccolo numero di componenti registrati in un contenitore DI rende il codice DI più facile da leggere e gonfiare.

Problemi correlati