Qual è il modo migliore per costruire una fabbrica usando NInject?


27

Mi sento abbastanza a mio agio con l'iniezione di dipendenza usando NInject in MVC3. Mentre lavoravo in un'applicazione MVC3, ho sviluppato una Controller Creation Factory personalizzata utilizzando NInject, quindi qualsiasi controller creato avrà delle dipendenze iniettate attraverso questa Factory Controller.

Ora sto iniziando a sviluppare un'applicazione Windows, voglio usare Application Wide Dependency Injection. vale a dire che ogni oggetto deve essere creato tramite NInject, in modo da facilitare Unit Testing. Per favore guidami per assicurarti che ogni oggetto creato debba essere solo NInject Factory.

Ad esempio, se su qualsiasi modulo di Windows Button_Clicksull'evento scrivo:

TestClass testClass = new TestClass()

e TestClassha delle dipendenze, diciamo, ITestquindi deve essere automaticamente risolto. So di poter usare:

Ikernel kernel = new StandardKenel()
//AddBinding()
TestClass testClass = kenel.get<TestClass>();

Ma trovo noioso farlo ogni volta che voglio creare un oggetto. Inoltre costringe lo sviluppatore a creare l'oggetto in un modo particolare. Può essere migliorato?

Posso avere un repository centrale per la creazione di oggetti e quindi ogni creazione di oggetti utilizzerà automaticamente quel repository?


1
Ciao Pravin Patil: ottima domanda. Ho apportato una piccola modifica al tuo titolo per rendere più chiaro ciò che stai chiedendo; sentiti libero di modificare se ho perso il segno.

@MarkTrapp: Grazie per il titolo adatto. Ho perso quel motto ...
Pravin Patil,

Come nota a margine, il progetto è scritto "Ninject", non "NInject". Anche se è possibile che sia stato En-Inject, al giorno d'oggi giocano un po 'sul tema del nin-ja. :) Cfr. ninject.org
Cornelius,

Risposte:


12

Per le applicazioni client, spesso è meglio adattare un modello come MVP (o MVVM) e utilizzare l'associazione dati dal modulo al ViewModel o al Presenter sottostante.

Per i ViewModels, è possibile iniettare le dipendenze richieste utilizzando l'iniezione standard del costruttore.

Nella Root di composizione dell'applicazione è possibile collegare l' intero grafico degli oggetti per l'applicazione. Non è necessario utilizzare un contenitore DI (come Ninject) per questo, ma è possibile.


7

Le applicazioni Windows Form hanno in genere un punto di ingresso simile al seguente:

    // Program.cs
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }

Se si sottolinea questo punto nel codice come radice della composizione , è possibile ridurre notevolmente il numero di posizioni in cui è presente il codice che richiama esplicitamente Ninject come se fosse un Service Locator.

    // Program.cs
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        var kernel = InitializeNinjectKernel();
        Application.Run(kernel.Get<MainForm>());
    }

Da questo punto, inietti tutte le tue dipendenze tramite l'iniezione del costruttore.

public MainForm(TestClass testClass) {
    _testClass = testClass;
}

Se la tua "dipendenza" è qualcosa che devi essere in grado di produrre più volte, allora ciò di cui hai veramente bisogno è una fabbrica:

public MainForm(IFactory<TestClass> testClassFactory) {
    _testClassFactory = testClassFactory;
}

...
var testClass = _testClassFactory.Get();

Puoi implementare l'interfaccia IFactory in questo modo per evitare di dover creare tantissime implementazioni una tantum:

public class InjectionFactory<T> : IFactory<T>, IObjectFactory<T>, IDependencyInjector<T>
{
    private readonly IKernel _kernel;
    private readonly IParameter[] _contextParameters;

    public InjectionFactory(IContext injectionContext)
    {
        _contextParameters = injectionContext.Parameters
            .Where(p => p.ShouldInherit).ToArray();
        _kernel = injectionContext.Kernel;
    }

    public T Get()
    {
        try
        {
            return _kernel.Get<T>(_contextParameters.ToArray());
        }
        catch (Exception e)
        {
            throw new Exception(
                string.Format("An error occurred while attempting to instantiate an object of type <{0}>",
                typeof(T)));
        }
    }

...
Bind(typeof (IFactory<>)).To(typeof (InjectionFactory<>));
Bind(typeof (IContext)).ToMethod(c => c.Request.ParentContext);

Per favore, hai la piena implementazione per questa fabbrica?
Tebo,

@ColourBlend: No, ma se ti sbarazzi delle altre interfacce che avevo InjectionFactory<T>implementato, il codice fornito dovrebbe funzionare bene. C'è qualcosa in particolare con cui hai problemi?
StriplingWarrior il

L'ho già implementato, voglio solo sapere se c'erano altre cose interessanti all'interno della classe.
Tebo,

@Tebo: ho appena avuto l'implementazione di un paio di altre interfacce relative a DI, come una fabbrica che potresti passare Typea, ma che garantirebbe che gli oggetti che ha idratato per quell'attrezzo Typeo estendere un determinato tipo generico. Niente di speciale.
StriplingWarrior l'

4

Scrivo sempre un wrapper Adapter per qualsiasi contenitore IoC, che assomiglia a questo:

public static class Ioc
{
    public static IIocContainer Container { get; set; }
}

public interface IIocContainer 
{
    object Get(Type type);
    T Get<T>();
    T Get<T>(string name, string value);
    void Inject(object item);
    T TryGet<T>();
}

Per Ninject, in particolare, la classe Adapter concreta è simile alla seguente:

public class NinjectIocContainer : IIocContainer
{
    public readonly IKernel Kernel;
    public NinjectIocContainer(params INinjectModule[] modules) 
    {
        Kernel = new StandardKernel(modules);
        new AutoWirePropertyHeuristic(Kernel);
    }

    private NinjectIocContainer()
    {
        Kernel = new StandardKernel();
        Kernel.Load(AppDomain.CurrentDomain.GetAssemblies());

        new AutoWirePropertyHeuristic(Kernel);
    }

    public object Get(Type type)
    {
        try
        {
            return Kernel.Get(type);
        }
        catch (ActivationException exception)
        {
            throw new TypeNotResolvedException(exception);
        }              
    }

    public T TryGet<T>()
    {
        return Kernel.TryGet<T>();
    }

    public T Get<T>()
    {
        try
        {
            return Kernel.Get<T>();
        }
        catch (ActivationException exception)
        {
            throw new TypeNotResolvedException(exception);
        }           
    }

    public T Get<T>(string name, string value)
    {
        var result = Kernel.TryGet<T>(metadata => metadata.Has(name) &&
                     (string.Equals(metadata.Get<string>(name), value,
                                    StringComparison.InvariantCultureIgnoreCase)));

        if (Equals(result, default(T))) throw new TypeNotResolvedException(null);
            return result;
    }

    public void Inject(object item)
    {
        Kernel.Inject(item);
    }
}

Il motivo principale per farlo è quello di astrarre il framework IoC, quindi posso sostituirlo in qualsiasi momento, dato che la differenza tra i framework è generalmente nella configurazione piuttosto che nell'uso.

Ma, come bonus, le cose diventano anche molto più facili per l'utilizzo del framework IoC all'interno di altri framework che non lo supportano intrinsecamente. Per WinForms, ad esempio, sono due passaggi:

Nel tuo metodo principale, è sufficiente creare un'istanza di un contenitore prima di fare qualsiasi altra cosa.

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        try
        {
            Ioc.Container = new NinjectIocContainer( /* include modules here */ );
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MyStartupForm());
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }
}

E quindi avere una Forma base, da cui derivano altre forme, che chiama Inject su se stessa.

public IocForm : Form
{
    public IocForm() : base()
    {
        Ioc.Container.Inject(this);
    }
}

Questo dice all'euristica del cablaggio automatico di tentare di iniettare ricorsivamente tutte le proprietà nel modulo che si adattano alle regole impostate nei moduli.


Soluzione molto bella ..... Ci proverò.
Pravin Patil,

10
Questo è un Service Locator, che è una pessima idea: blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
Mark Seemann

2
@MarkSeemann: un localizzatore di servizi è una cattiva idea, se si accede ad esso da qualsiasi luogo, invece di lasciarlo collegare gli oggetti di livello più alto possibile. Leggi lo stesso commento di Mark, un po 'più in basso nella pagina: "In questi casi non hai davvero ricorso ma per spostare la Root di composizione in ciascun oggetto (ad es. Pagina) e lasciare che il tuo contenitore DI leghi le tue dipendenze da lì. Potrebbe sembrare l'anti-pattern di Service Locator, ma non è perché si mantiene comunque l'utilizzo del contenitore al minimo assoluto. " (Modifica: Aspetta, SEI Mark! Quindi qual è la differenza?)
pdr

1
La differenza è che puoi comunque proteggere il resto della tua base di codice dal Composer, invece di rendere disponibile un Localizzatore servizi Singleton per qualsiasi classe.
Mark Seemann,

2
@pdr: nella mia esperienza, se stai tentando di iniettare servizi in cose come le classi di attributi, non stai separando correttamente le preoccupazioni. Ci sono casi in cui il framework che stai utilizzando rende praticamente impossibile usare l'iniezione di dipendenza corretta, e talvolta siamo costretti a usare un localizzatore di servizi, ma proverei sicuramente a prendere il vero DI il più possibile prima di tornare a questo modello.
StriplingWarrior,

1

Un buon uso di Dependency Injection solitamente si basa sulla separazione del codice che crea oggetti e la logica aziendale effettiva. In altre parole, non vorrei che il mio team usasse newe creasse frequentemente un'istanza di una classe in quel modo. Una volta fatto, non c'è modo di scambiare facilmente quel tipo creato con un altro, poiché hai già specificato il tipo concreto.

Quindi ci sono due modi per risolvere questo problema:

  1. Iniettare le istanze necessarie per una classe. Nel tuo esempio, inserisci a TestClassnel tuo Windows Form in modo che abbia già un'istanza quando ne ha bisogno. Quando Ninject crea un'istanza del modulo, crea automaticamente la dipendenza.
  2. Nei casi in cui non si desidera davvero creare un'istanza fino a quando non è necessaria, è possibile inserire una factory nella logica aziendale. Ad esempio, puoi iniettare un IKernelnel tuo Windows Form e quindi usarlo per creare un'istanza TestClass. A seconda del tuo stile, ci sono anche altri modi per farlo (iniettando una classe di fabbrica, un delegato di fabbrica, ecc.).

In questo modo è facile sostituire sia il tipo concreto di TestClass, sia modificare la costruzione effettiva della classe di test, senza modificare effettivamente il codice che utilizza la classe di test.


1

Non ho usato Ninject, ma il modo standard di creare roba quando si utilizza un IoC è farlo tramite un Func<T>dove si Ttrova il tipo di oggetto che si desidera creare. Quindi, se l'oggetto T1deve creare oggetti di tipo, T2il costruttore di T1deve avere un parametro di tipo Func<T1>che viene quindi memorizzato come campo / proprietà di T2. Ora, quando si desidera creare oggetti di tipo T2in T1si richiama il Func.

Questo ti disaccoppia completamente dal tuo framework IoC ed è il modo giusto per codificare in una mentalità IoC.

Il rovescio della medaglia nel fare questo è che può diventare fastidioso quando devi collegare manualmente un Funcesempio o quando il tuo creatore richiede alcuni parametri, quindi l'IoC non può autowire Functe per te.

http://code.google.com/p/autofac/wiki/RelationshipTypes

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.