Який найкращий спосіб побудувати завод за допомогою NInject?


27

Мені дуже зручно введення залежності, використовуючи NInject в MVC3. Працюючи в додатку MVC3, я розробив власну фабрику створення контролерів за допомогою NInject, тому будь-який створений контролер матиме залежність, введені в нього через цю Фабрику контролерів.

Зараз я починаю розробляти додаток для Windows, я хочу використовувати широкозахисну програму Dependency Injection. тобто кожен об'єкт повинен бути створений за допомогою NInject, щоб полегшити тестування одиниць. Підкажіть, будь ласка, переконайтеся, що кожен створений об'єкт повинен бути лише заводу NInject.

Наприклад, якщо в будь-якому вікні форми на Button_Clickподію я пишу:

TestClass testClass = new TestClass()

і TestClassє якась залежність від, скажімо, ITestтоді вона повинна бути автоматично вирішена. Я знаю, що можу використовувати:

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

Але мені здається, що це нудно робити кожен раз, коли я хочу створити об'єкт. Це також змушує розробника створювати об'єкт певним чином. Чи можна зробити це краще?

Чи можу я мати центральне сховище для створення об’єктів, і тоді кожне створення об'єкта автоматично використовуватиме це сховище?


1
Привіт Правдін Патіл: чудове запитання. Я змінив ваш титул, щоб зрозуміти, про що ви просите; не соромтесь змінювати, якщо я пропустив позначку.

@MarkTrapp: Дякую за відповідну назву. Я пропустив цю мітку ...
Pravin Patil

Як незначну сторону, в проекті написано "Ninject", а не "NInject". Хоча може бути, що це було En-Inject, вони сьогодні грають на тему nin-ja зовсім небагато. :) Ср. ninject.org
Корнелій

Відповіді:


12

Для клієнтських додатків найчастіше найкраще адаптувати зразок типу MVP (або MVVM) та використовувати прив'язку даних від форми до базового ViewModel або Presenter.

Для ViewModels можна вводити необхідні залежності, використовуючи стандартну інжекцію конструктора.

У складі Root Composite Root можна з'єднати весь графік об'єкта для вашої програми. Для цього вам не потрібно використовувати контейнер DI (наприклад, Ninject), але ви можете.


7

Програми Windows Forms зазвичай мають такий пункт входу, який виглядає приблизно так:

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

Якщо ви вкажете цей код у вашому корінні композиції , ви можете значно зменшити кількість місць, де ви маєте явний посилання на Ninject, як ніби це Локатор обслуговування.

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

З цього моменту ви вводите всі свої залежності за допомогою конструкторської інжекції.

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

Якщо ваша "залежність" - це те, що вам потрібно мати можливість виробляти кілька разів, то вам дійсно потрібно завод:

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

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

Ви можете реалізувати інтерфейс IFactory таким чином, щоб уникнути необхідності створення тони разових реалізацій:

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

Будь ласка, чи маєте ви повну реалізацію для цієї фабрики?
Тебо

@ColourBlend: Ні, але якщо ви позбудетесь інших інтерфейсів, які я InjectionFactory<T>реалізував, то наданий код повинен працювати чудово. Чи є щось зокрема, з чим у вас виникають проблеми?
StriplingWarrior

Я вже реалізував це, просто хочу знати, чи були в класі ще цікаві речі.
Тебо

@Tebo: Мені просто довелося реалізувати пару інших інтерфейсів, пов'язаних з DI, як завод, до якого можна передати Type, але який би гарантував, що об'єкти, які він гідратавав для цього, Typeреалізують або розширюють заданий загальний тип. Нічого занадто особливого.
Стрипінг-воїн

4

Я завжди пишу обгортку адаптера для будь-якого контейнера IoC, який виглядає приблизно так:

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

Конкретно для Ninject клас конкретного адаптера виглядає так:

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

Основна причина цього - абстрагувати рамку IoC, тому я можу її замінити в будь-який час - враховуючи, що різниця між фреймворками, як правило, конфігурація, а не використання.

Але, як бонус, речі також стають набагато простішими для використання IoC фрейму всередині інших рамок, які не породжують це. Наприклад, для WinForms це два етапи:

У головному методі просто інстанціюйте контейнер, перш ніж робити що-небудь інше.

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

А потім мати базову Форму, з якої випливають інші форми, яка викликає Ін'єкцію на себе.

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

Це підказує евристиці автоматичного підключення проби рекурсивно вводити всі властивості у форму, яка відповідає правилам, встановленим у ваших модулях.


Дуже приємне рішення ..... Спробую.
Pravin Patil

10
Це Локатор послуг, що є надзвичайно поганою ідеєю: blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
Марк

2
@MarkSeemann: Локатор сервісу - це погана ідея, якщо ви отримуєте доступ до нього звідусіль, замість того, щоб дозволяти йому передавати об'єкти верхнього рівня якнайдалі. Прочитайте власний коментар Марка трохи вниз по сторінці: "У таких випадках у вас дійсно немає звернення, але перенести Корінь композиції в кожен об'єкт (наприклад, Сторінка) і дозволити вашому контейнерові" DI "виправити залежність від цього. Це може виглядати як анти-візерунок "Локатор послуг", але це не тому, що ви все ще підтримуєте використання контейнерів на абсолютному мінімумі. " (Редагувати: Зачекайте, ви - Марк! Тож яка різниця?)
пдр

1
Різниця полягає в тому, що ви все ще можете захистити решту своєї кодової бази від композитора, замість того, щоб зробити Singleton Locator послуг доступним для будь-якого класу.
Марк Семанн

2
@pdr: На мій досвід, якщо ви намагаєтеся ввести послуги в такі речі, як класи атрибутів, ви не розділяєте проблеми належним чином. Бувають випадки, коли рамки, які ви використовуєте, практично неможливо використовувати належну ін'єкцію залежності, і іноді ми змушені використовувати сервіс-локатор, але я обов'язково спробую зробити справжню DI якомога далі, перш ніж повернутися до цього візерунок.
Стриптинг-воїн

1

Добре використання ін'єкційних залежностей зазвичай покладається на відокремлення коду, який створює об'єкти та фактичну логіку бізнесу. Іншими словами, я не хотів би, щоб моя команда часто використовувала newта створювала примірник класу таким чином. Як тільки це буде зроблено, неможливо легко замінити створений тип на інший, оскільки ви вже вказали конкретний тип.

Отже, існує два шляхи вирішити це:

  1. Введіть екземпляри, які потрібні класу. У вашому прикладі введіть a TestClassу форму Windows, щоб він вже мав примірник, коли йому це потрібно. Коли Ninject створює форму, вона автоматично створює залежність.
  2. У тих випадках, коли ви дійсно не хочете створювати екземпляр, поки вам це не потрібно, ви можете ввести фабрику в бізнес-логіку. Наприклад, ви можете ввести форму IKernelу форму Windows, а потім використати її для інстанції TestClass. Залежно від вашого стилю, є й інші способи досягти цього (введення заводського класу, заводського делегата тощо).

Це дозволяє легко замінити як конкретний тип TestClass, так і змінити фактичну конструкцію тестового класу, не змінюючи фактично код, який використовує тестовий клас.


1

Я не використовував Ninject, але стандартний спосіб створення речей, коли ви використовуєте IoC, - це робити через a, Func<T>де Tє тип об'єкта, який ви хочете створити. Отже, якщо об'єкту T1потрібно створити об'єкти типу, T2то конструктор T1повинен мати параметр типу, Func<T1>який потім зберігається як поле / властивість T2. Тепер, коли ви хочете створити об'єкти типу T2у T1вас, ви викликаєте Func.

Це повністю від'єднує вас від вашої IoC-системи та є правильним способом кодування в наборі даних IoC.

Недоліком цього є те, що це може набриднути, коли вам потрібно вручну підключити Funcs або приклад, коли ваш творець вимагає певного параметра, тож IoC не може автоматично Funcнадати вам провід .

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.