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

27

Sto abbastanza bene con l'iniezione delle dipendenze usando NInject in MVC3. Mentre lavoravo in un'applicazione MVC3, ho sviluppato una Custom Creation Factory personalizzata usando NInject, quindi ogni controller creato avrà delle dipendenze iniettate in esso attraverso questo Controller Factory.

Ora sto iniziando a sviluppare un'applicazione Windows, voglio usare Application Dependency Injection. Ad esempio, ogni oggetto deve essere creato tramite NInject, in modo da facilitare il Test unità. Per favore guidami per assicurarti che ogni oggetto creato debba essere solo NINject Factory.

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

TestClass testClass = new TestClass()

e TestClass ha una dipendenza, diciamo, ITest , quindi deve essere automaticamente risolta. So che posso usare:

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

Ma trovo noioso farlo ogni volta che voglio creare un oggetto. Inoltre, lo sviluppatore delle forze crea 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?

    
posta Pravin Patil 07.02.2012 - 12:38
fonte

5 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 Presenter sottostante.

Per ViewModels, puoi iniettare le dipendenze richieste usando Iniezione costruttore standard.

Nella Root di composizione della tua applicazione puoi collegare grafico degli oggetti in uso per la tua applicazione. Non è necessario utilizzare un contenitore DI (ad esempio Ninject), ma è possibile.

    
risposta data 07.02.2012 - 17:27
fonte
7

In genere le applicazioni Windows Form hanno un punto di ingresso simile a questo:

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

Se metti questo punto in codice nella radice di composizione , puoi ridurre notevolmente il numero di punti in cui hai il codice che richiama esplicitamente Ninject come se fosse un Localizzatore di servizio.

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

Da questo punto, si iniettano tutte le proprie 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 quello di cui hai veramente bisogno è una fabbrica:

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

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

In questo modo puoi implementare l'interfaccia IFactory per evitare di dover creare una tonnellata di 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);
    
risposta data 07.02.2012 - 19:51
fonte
4

Scrivo sempre un wrapper adattatore 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 dell'adattatore concreto si presenta così:

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

La ragione principale per fare ciò è di astrarre il framework IoC, così posso sostituirlo in qualsiasi momento - dato che la differenza tra i framework è generalmente nella configurazione piuttosto che nell'utilizzo.

Ma, come bonus, le cose diventano anche molto più semplici per usare il framework IoC all'interno di altri framework che non lo supportano intrinsecamente. Ad esempio, per WinForms sono due passaggi:

Nel tuo metodo Main, crea semplicemente 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 poi 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.

    
risposta data 07.02.2012 - 14:46
fonte
1

Il buon uso di Dependency Injection si basa solitamente sulla separazione del codice che crea oggetti e sulla logica di business effettiva. In altre parole, vorrei che non desiderassi il mio team di utilizzare frequentemente new e creare un'istanza di una classe in questo modo. Una volta fatto, non c'è modo di scambiare facilmente quel tipo creato con un altro, dato che hai già specificato il tipo concreto.

Quindi ci sono due modi per risolvere questo problema:

  1. Inietti le istanze necessarie per una classe. Nel tuo esempio, inserisci una TestClass nel 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 non vuoi creare un'istanza finché non ne hai bisogno, puoi inserire una fabbrica nella logica aziendale. Ad esempio, puoi inserire un IKernel nel tuo Windows Form e usarlo per creare un'istanza di TestClass . A seconda del tuo stile, ci sono altri modi per farlo (iniettando una classe di fabbrica, un delegato di fabbrica, ecc.)

Facendo ciò è facile scambiare sia il tipo concreto di TestClass, sia modificare la costruzione effettiva della classe di test, senza realmente modificare il codice che usa la classe di test.

    
risposta data 07.02.2012 - 14:36
fonte
1

Non ho usato Ninject, ma il modo standard di creare cose quando usi un IoC è farlo tramite un Func<T> dove T è il tipo di oggetto che vuoi creare. Quindi se object T1 ha bisogno di creare oggetti di tipo T2 , allora il costruttore di T1 deve avere il parametro di tipo Func<T1> che viene quindi memorizzato come campo / proprietà di T2 . Ora quando vuoi creare oggetti di tipo T2 in T1 invochi Func .

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

Lo svantaggio di fare questo è che può diventare fastidioso quando devi collegare manualmente Func s o esempio quando il tuo creatore richiede alcuni parametri, quindi IoC non può autowire il Func per te.

link

    
risposta data 07.02.2012 - 18:37
fonte