Apa cara terbaik untuk membangun pabrik menggunakan NInject?

27

Saya cukup nyaman dengan injeksi ketergantungan menggunakan NInject di MVC3. Saat bekerja di aplikasi MVC3, saya mengembangkan Pabrik Pembuatan Pengendali kustom menggunakan NInject, sehingga setiap pengontrol yang dibuat akan memiliki ketergantungan yang disuntikkan di dalamnya melalui Pabrik Pengendali ini.

Sekarang saya mulai mengembangkan aplikasi windows, saya ingin menggunakan Aplikasi Dependency Injection yang luas. yaitu Setiap objek harus dibuat melalui NInject, sehingga memudahkan Unit Testing. Tolong bimbing saya untuk memastikan bahwa setiap objek yang dibuat harus melalui Pabrik NInject saja.

Sebagai contoh, jika pada bentuk windows apa pun pada Button_Clickacara saya menulis:

TestClass testClass = new TestClass()

dan TestClassmemiliki ketergantungan pada, katakanlah, ITestmaka itu harus diselesaikan secara otomatis. Saya tahu saya bisa menggunakan:

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

Tetapi saya merasa membosankan untuk melakukan ini setiap kali saya ingin membuat objek. Ini juga memaksa pengembang untuk membuat objek dengan cara tertentu. Bisakah itu dibuat lebih baik?

Bisakah saya memiliki repositori pusat untuk pembuatan objek dan kemudian setiap pembuatan objek akan secara otomatis menggunakan repositori itu?

Pravin Patil
sumber
1
Hai Pravin Patil: pertanyaan bagus. Saya membuat perubahan kecil pada judul Anda untuk membuatnya lebih jelas tentang apa yang Anda minta; jangan ragu untuk memodifikasi jika saya melewatkan sasaran.
@MarkTrapp: Terima kasih atas judul yang sesuai. Saya melewatkan tagline itu ...
Pravin Patil
Sebagai catatan kecil, proyek ini dieja "Ninject", bukan "NInject". Meskipun mungkin sudah En-Inject, mereka bermain di tema nin-ja sedikit saat ini. :) Lih. ninject.org
Cornelius

Jawaban:

12

Untuk aplikasi klien, seringkali yang terbaik adalah mengadaptasi pola seperti MVP (atau MVVM) dan menggunakan data-binding dari formulir ke ViewModel atau Presenter yang mendasarinya.

Untuk ViewModels, Anda dapat menyuntikkan dependensi yang diperlukan menggunakan Injeksi Konstruktor standar.

Dalam Komposisi Root aplikasi Anda, Anda dapat memasang seluruh grafik objek untuk aplikasi Anda. Anda tidak perlu menggunakan DI Container (seperti Ninject) untuk ini, tetapi Anda bisa.

Mark Seemann
sumber
7

Aplikasi Windows Forms biasanya memiliki titik masuk yang terlihat seperti ini:

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

Jika Anda menyatakan hal ini dalam kode root komposisi Anda, Anda dapat sangat mengurangi jumlah tempat di mana Anda memiliki kode yang secara eksplisit memanggil Ninject seolah-olah itu adalah Pencari Layanan.

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

Dari titik ini, Anda menyuntikkan semua dependensi Anda melalui injeksi konstruktor.

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

Jika "ketergantungan" Anda adalah sesuatu yang Anda harus dapat hasilkan berkali-kali, maka yang benar-benar Anda butuhkan adalah pabrik:

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

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

Anda dapat mengimplementasikan antarmuka IFactory dengan cara ini untuk menghindari keharusan membuat satu ton implementasi satu kali:

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);
StriplingWarrior
sumber
Tolong, apakah Anda memiliki implementasi penuh untuk pabrik ini?
Tebo
@ ColourBlend: Tidak, tetapi jika Anda menyingkirkan antarmuka lain yang telah saya InjectionFactory<T>implementasikan, maka kode yang disediakan harus bekerja dengan baik. Apakah ada sesuatu yang Anda alami masalah?
StriplingWarrior
Saya sudah menerapkannya, saya hanya ingin tahu apakah ada hal-hal menarik lainnya di dalam kelas.
Tebo
@Tebo: Saya baru saja mengimplementasikan beberapa antarmuka terkait-DI lainnya, seperti pabrik yang bisa Anda gunakan Type, tetapi yang akan menjamin bahwa objek yang terhidrasi untuk Typepenerapan itu atau memperluas jenis generik yang diberikan. Tidak ada yang terlalu istimewa.
StriplingWarrior
4

Saya selalu menulis bungkus Adaptor untuk Kontainer IoC, yang terlihat seperti ini:

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

Untuk Ninject, khususnya, kelas Adapter beton terlihat seperti ini:

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

Alasan utama untuk melakukan ini adalah untuk abstrak kerangka kerja IoC, jadi saya bisa menggantinya kapan saja - mengingat bahwa perbedaan antara kerangka kerja umumnya dalam konfigurasi daripada penggunaan.

Tetapi, sebagai bonus, banyak hal juga menjadi jauh lebih mudah untuk menggunakan kerangka kerja IoC di dalam kerangka kerja lain yang tidak secara inheren mendukungnya. Untuk WinForms, misalnya, ini adalah dua langkah:

Dalam metode Utama Anda, cukup instantiate sebuah wadah sebelum melakukan hal lain.

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

Dan kemudian memiliki Formulir dasar, dari mana bentuk lain berasal, yang memanggil Suntikan itu sendiri.

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

Ini memberi tahu heuristik pengkabelan otomatis untuk mencoba menyuntikkan semua properti secara rekursif dalam bentuk yang sesuai dengan aturan yang diatur dalam modul Anda.

pdr
sumber
Solusi yang sangat bagus ..... Saya akan mencobanya.
Pravin Patil
10
Itu adalah Service Locator, yang merupakan ide yang sangat buruk: blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
Mark Seemann
2
@MarkSeemann: Pencari layanan adalah ide yang buruk, jika Anda mengaksesnya dari mana saja, alih-alih membiarkannya memasang objek tingkat atas sejauh mungkin. Baca komentar Mark sendiri, sedikit jauh di bawah halaman: "Dalam kasus seperti itu Anda benar-benar tidak memiliki jalan lain kecuali untuk memindahkan Komposisi Root ke setiap objek (misalnya Halaman) dan biarkan Wadah Anda memasang dependensi Anda dari sana. Ini mungkin terlihat seperti Service Locator anti-pattern, tetapi itu bukan karena Anda masih menjaga penggunaan kontainer seminimal mungkin. " (Edit: Tunggu, Anda ADALAH Mark Jadi apa bedanya!?)
pdr
1
Perbedaannya adalah bahwa Anda masih dapat melindungi sisa basis kode Anda dari Komposer, alih-alih menjadikan Singleton Service Locator tersedia untuk semua kelas.
Mark Seemann
2
@ pdr: Dalam pengalaman saya, jika Anda mencoba menyuntikkan layanan ke hal-hal seperti kelas atribut, Anda tidak memisahkan kekhawatiran dengan benar. Ada kasus di mana kerangka kerja yang Anda gunakan membuatnya secara praktis tidak mungkin untuk menggunakan injeksi dependensi yang tepat, dan kadang-kadang kami terpaksa menggunakan pencari lokasi layanan, tapi saya pasti akan mencoba mengambil DI true sejauh mungkin sebelum kembali ke ini pola.
StriplingWarrior
1

Penggunaan Dependency Injection yang baik biasanya bergantung pada pemisahan kode yang menciptakan objek dan logika bisnis yang sebenarnya. Dengan kata lain, saya tidak ingin tim saya sering menggunakan newdan membuat instance kelas seperti itu. Setelah selesai, tidak ada cara untuk dengan mudah menukar tipe yang dibuat untuk yang lain, karena Anda telah menentukan tipe konkret.

Jadi ada dua cara untuk memperbaiki ini:

  1. Suntikkan instance yang dibutuhkan kelas. Dalam contoh Anda, menyuntikkan TestClasske dalam Formulir Windows Anda sehingga sudah memiliki contoh saat dibutuhkan. Ketika Ninject instantiate formulir Anda, itu secara otomatis membuat ketergantungan.
  2. Dalam kasus di mana Anda benar - benar tidak ingin membuat contoh sampai Anda membutuhkannya, Anda dapat menyuntikkan pabrik ke dalam logika bisnis. Misalnya, Anda bisa menyuntikkan IKernelke dalam Formulir Windows Anda, dan kemudian menggunakannya untuk instantiate TestClass. Tergantung pada gaya Anda, ada cara lain untuk mencapai ini juga (menyuntikkan kelas pabrik, delegasi pabrik, dll).

Melakukan hal ini membuatnya mudah untuk menukar tipe konkret dari TestClass, serta memodifikasi konstruksi sebenarnya dari kelas uji, tanpa benar-benar memodifikasi kode yang menggunakan kelas uji.

Chris Pitman
sumber
1

Saya belum pernah menggunakan Ninject, tetapi cara standar untuk membuat barang ketika Anda menggunakan IoC adalah melakukannya melalui di Func<T>mana Tjenis objek yang ingin Anda buat. Jadi jika objek T1perlu membuat objek bertipe T2maka konstruktor T1harus memiliki parameter bertipe Func<T1>yang kemudian disimpan sebagai bidang / properti T2. Sekarang ketika Anda ingin membuat objek dari jenis T2di T1Anda memohon Func.

Ini benar-benar memisahkan Anda dari kerangka kerja IoC Anda dan merupakan cara yang tepat untuk kode dalam pola pikir IoC.

Kelemahan dari melakukan ini adalah bahwa hal itu dapat mengganggu ketika Anda perlu secara manual kawat Funcatau contoh ketika pencipta Anda memerlukan beberapa parameter, sehingga IoC tidak dapat autowire Funcuntuk Anda.

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


sumber