2009-12-12 47 views
8

Una nuova iniezione di dipendenza e sto cercando di capire se si tratta di un pattern anti.Iniettare l'iniettore delle dipendenze usando l'iniezione delle dipendenze

Diciamo che ho 3 gruppi:

Foo.Shared - this has all the interfaces 
Foo.Users - references Foo.Shared 
Foo.Payment - references Foo.Shared 

Foo.Users ha bisogno di un oggetto che è costruito all'interno Foo.Payment, e Foo.Payment ha bisogno anche roba da Foo.Users. Questo crea una sorta di dipendenza circolare.

Ho definito un'interfaccia in Foo.Shared che esegue il proxy del framework di Dipendenza Injection che sto utilizzando (in questo caso NInject).

public interface IDependencyResolver 
{ 
    T Get<T>(); 
} 

nell'applicazione contenitore, ho un'implementazione di questa interfaccia:

public class DependencyResolver:IDependencyResolver 
{ 
    private readonly IKernel _kernel; 

    public DependencyResolver(IKernel kernel) 
    { 
     _kernel = kernel; 
    } 

    public T Get<T>() 
    { 
     return _kernel.Get<T>(); 
    } 
} 

La configurazione è simile al seguente:

public class MyModule:StandardModule 
{ 
    public override void Load() 
    { 
     Bind<IDependencyResolver>().To<DependencyResolver>().WithArgument("kernel", Kernel); 
     Bind<Foo.Shared.ISomeType>().To<Foo.Payment.SomeType>(); // <- binding to different assembly 
     ... 
    } 
} 

Questo mi permette di creare un'istanza di un nuovo oggetto di Foo.Payment.SomeType dall'interno di Foo.Users senza bisogno di un riferimento diretto:

public class UserAccounts:IUserAccounts 
{ 
    private ISomeType _someType; 
    public UserAccounts(IDependencyResolver dependencyResolver) 
    { 
     _someType = dependencyResolver.Get<ISomeType>(); // <- this essentially creates a new instance of Foo.Payment.SomeType 
    } 
} 

Ciò rende poco chiaro quali siano esattamente le dipendenze della classe UserAccounts in questa istanza, il che mi fa pensare che non sia una buona pratica.

In quale altro modo posso ottenere questo risultato?

Qualche idea?

+1

+1 solo per il titolo di scioglilingua. – womp

+0

stesso, adoro il titolo :) –

risposta

7

Anche se un po 'controverso: sì, questo è un anti-modello. E 'noto come un Service Locatore mentre alcuni lo considerano un modello di progettazione adeguata, ritengo che un anti-modello.

Questo problema è che l'utilizzo di esempio la classe UserAccounts diventa implicita anziché esplicita. Mentre il costruttore afferma che ha bisogno di un IDependencyResolver, non dice cosa dovrebbe andare in esso. Se si passa un IDependencyResolver che non può risolvere ISomeType, verrà lanciato.

Ciò che è peggio è che in iterazioni successive, si potrebbe essere tentati di risolvere alcuni altro tipo da UserAccounts. Compilerà bene, ma è probabile che vada in fase di esecuzione se/quando il tipo non può essere risolto.

Non andare quell'itinerario.

Dalle informazioni fornite, è impossibile dirvi esattamente come dovreste risolvere il vostro particolare problema con le dipendenze circolari, ma suggerirei di ripensare il vostro progetto. In molti casi, i riferimenti circolari sono un sintomo di Leaky Astrazioni, quindi forse se si rimodellare la vostra API un po ', andrà via - è spesso sorprendente come piccolo sono necessarie modifiche.

In generale, la soluzione a qualsiasi problema è l'aggiunta di un altro livello di riferimento indiretto. Se è veramente necessario disporre di oggetti da entrambe le librerie che collaborano strettamente, è possibile in genere introdurre un broker intermedio.

  • In molti casi, un publish/subscribe modello funziona bene.
  • Il modello Mediatore può fornire un'alternativa se la comunicazione deve essere in entrambe le direzioni.
  • È anche possibile introdurre una fabbrica astratta per recuperare l'istanza necessaria in base alle esigenze, anziché richiederne il cablaggio immediato.
+0

Questo è quello che pensavo che le dipendenze non siano chiare, quindi deve essere un antipattern. :) Una fabbrica astratta è stata la mia prima scelta, ma ha complicato il codice. Nella vera applicazione ho bisogno di creare molti tipi separati, non solo uno. O eseguo l'hardcode di ogni metodo di fabbrica diverso o uso i generici per associare un'interfaccia a una classe concreta (anche codificata). Ma in questo modo perdo la potenza del framework di distribuzione delle dipendenze, e diventerò davvero disordinato per configurare i binding, fino al punto di dover usare il reflection/type resolver code della dipendenza manuale usando il reflection. – andreialecu

+0

C'è sempre un prezzo da pagare :) Non sono d'accordo che diventa complicato configurare il contenitore - potrebbe diventare piuttosto prolisso e prolisso, ma sarà composto da un sacco di codice quasi dichiarativo, che è un ottimo compromesso. Gran parte della longevità potrebbe essere in grado di risolvere con la configurazione basata sulla convenzione, in particolare se si finisce con molte Fabbriche astratte simili che devono essere tutte configurate allo stesso modo. Castle Windsor ha funzionalità che ti permettono di configurare per convenzione in poche semplici istruzioni: non so se NInject può farlo anche ... –

1

Sembra strano per me. È possibile separare la logica che ha bisogno di entrambi i riferimenti in una terza assemblea per rompere le dipendenze ed evitare il rischio?

2

Sono d'accordo con ForeverDebugging: sarebbe opportuno eliminare la dipendenza circolare. Vedere se è possibile separare le classi in questo modo:

  • Foo.Payment.dll: Le classi che si occupano solo di pagamento, non con gli utenti
  • Foo.Users.dll: classi che si occupano solo con gli utenti, non con pagamento
  • Foo.UserPayment.dll: Le classi che si occupano di entrambe pagamento e gli utenti

Allora avete un assembly che fa riferimento gli altri due, ma nessun cerchio delle dipendenze.

Se si dispone di una dipendenza circolare tra gli assembly, non significa necessariamente che si dispone di una dipendenza circolare tra le classi. Ad esempio, si supponga di avere queste dipendenze:

  • Foo.Users.UserAccounts dipende Foo.Shared.IPaymentHistory, che viene realizzato tramite Foo.Payment.PaymentHistory.
  • Una classe di pagamento diversa, Foo.Payment.PaymentGateway, dipende da Foo.Shared.IUserAccounts. IUserAccounts è implementato da Foo.Users.UserAccounts.

Assumere non ci sono altre dipendenze.

Qui c'è una cerchia di assiemi che dipendono l'uno dall'altro in fase di runtime nell'applicazione (anche se non dipendono l'uno dall'altro in fase di compilazione, poiché passano attraverso la DLL condivisa). Ma non esiste una cerchia di classi che dipendano l'una dall'altra, in fase di compilazione o in fase di esecuzione.

In questo caso, si dovrebbe comunque essere in grado di utilizzare normalmente il contenitore IoC, senza aggiungere un livello aggiuntivo di riferimento indiretto. Nel tuo MyModule, collega ogni interfaccia al tipo concreto appropriato. Fai in modo che ogni classe accetti le sue dipendenze come argomenti per il costruttore. Quando il codice dell'applicazione di primo livello richiede un'istanza di una classe, lascia che chieda il contenitore IoC per la classe. Lascia che il contenitore IoC si preoccupi di trovare tutto ciò che la classe dipende da.



Se lo fai finire con una dipendenza circolare tra le classi, probabilmente è necessario utilizzare l'iniezione di proprietà (iniezione aka setter) su una delle classi, invece di iniezione del costruttore. Non utilizzo Ninject, ma supporta l'iniezione di proprietà - here is the documentation.

Normalmente i contenitori IoC utilizzano l'iniezione del costruttore: passano le dipendenze al costruttore della classe che dipende da essi. Ma questo non funziona quando c'è una dipendenza circolare. Se le classi A e B dipendono l'uno dall'altro, avresti bisogno di passare un'istanza di classe A in al costruttore di classe B. Ma al fine di creare una A, è necessario passare un'istanza della classe B nel suo costruttore. È un problema di pollo e uova.

Con l'iniezione di proprietà, si comunica al contenitore IoC di chiamare prima il costruttore e quindi di impostare una proprietà sull'oggetto costruito. Normalmente questo è usato per dipendenze opzionali, come i logger. Ma potresti anche usarlo per rompere una dipendenza circolare tra due classi che richiedono l'una per l'altra.

Questo non è per niente carino e raccomando vivamente di rifattare le vostre lezioni per eliminare le dipendenze circolari.