Какой лучший способ построить фабрику, используя NInject?

27

Я довольно удобен для внедрения зависимостей, используя NInject в MVC3. Работая в приложении MVC3, я разработал собственную фабрику создания контроллеров, используя NInject, поэтому любой созданный контроллер будет иметь зависимости, введенные в него через эту фабрику контроллеров.

Сейчас я начинаю разрабатывать приложение для Windows, я хочу использовать Application Dependency Injection. каждый объект должен быть создан с помощью NInject, чтобы упростить модульное тестирование. Пожалуйста, помогите мне убедиться, что каждый созданный объект должен соответствовать только фабрике NInject.

Например, если в какой-либо форме окна на Button_Clickсобытие я пишу:

TestClass testClass = new TestClass()

и TestClassимеет какую-либо зависимость, скажем, ITestтогда это должно быть автоматически разрешено. Я знаю, что могу использовать:

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

Но я считаю утомительным делать это каждый раз, когда я хочу создать объект. Это также заставляет разработчика создавать объект определенным образом. Можно ли сделать это лучше?

Могу ли я иметь центральный репозиторий для создания объекта, и тогда каждое создание объекта будет автоматически использовать этот репозиторий?

Правин Патил
источник
1
Привет Правин Патил: отличный вопрос. Я сделал небольшое изменение в вашем названии, чтобы прояснить, о чем вы спрашиваете; не стесняйтесь изменять, если я пропустил отметку.
@MarkTrapp: Спасибо за подходящий заголовок. Я пропустил этот слоган ...
Правин Патил
Как второстепенное замечание, проект пишется как «Ninject», а не «NInject». Хотя, возможно, это был En-Inject, в настоящее время они играют на тему ниндзя совсем немного. :) Ср. ninject.org
Корнелиус

Ответы:

12

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

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

В корне композиции вашего приложения вы можете подключить весь граф объектов для своего приложения. Вам не нужно использовать 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);
StriplingWarrior
источник
Пожалуйста, у вас есть полная реализация для этой фабрики?
Тебо
@ColourBlend: Нет, но если вы избавитесь от других интерфейсов, которые я InjectionFactory<T>реализовывал, то предоставленный код должен работать просто отлично. Есть ли что-то конкретное, с чем у вас проблемы?
StriplingWarrior
Я уже реализовал это, я просто хочу знать, были ли другие интересные вещи в классе.
Tebo
@Tebo: Я только что реализовал несколько других интерфейсов, связанных с DI, например, фабрику, которой вы могли бы передать Type, но которая гарантировала бы, что объекты, которые он гидратировал для этого, Typeреализуют или расширяют данный универсальный тип. Ничего особенного.
StriplingWarrior
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 конкретный класс Adapter выглядит следующим образом:

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, например, это два шага:

В вашем методе Main просто создайте экземпляр контейнера, прежде чем делать что-либо еще.

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

И тогда есть базовая форма, из которой происходят другие формы, которая вызывает Inject для себя.

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

Это говорит эвристике автоматического подключения пытаться рекурсивно внедрить все свойства в форме, которые соответствуют правилам, установленным в ваших модулях.

прецизионный самописец
источник
Очень хорошее решение ..... Я попробую.
Правин Патил
10
Это Сервисный Локатор, который является чрезвычайно
Марк
2
@MarkSeemann: поиск сервисов - плохая идея, если вы обращаетесь к нему отовсюду, а не позволяете подключать объекты верхнего уровня как можно ниже. Прочтите собственный комментарий Марка, немного вниз по странице: «В таких случаях у вас действительно нет другого выхода, кроме как переместить корень композиции в каждый объект (например, страницу) и позволить вашему DI-контейнеру связать ваши зависимости оттуда. Это может выглядеть так анти-паттерн Service Locator, но это не потому, что вы по-прежнему сохраняете минимальное использование контейнера ». (Правка: Подожди, ты, Марк! Так в чем же разница?)
pdr
1
Разница в том, что вы все еще можете защитить остальную часть своего кода от Composer вместо того, чтобы сделать локатор службы Singleton доступным для любого класса.
Марк Симанн
2
@pdr: По моему опыту, если вы пытаетесь внедрить сервисы в такие вещи, как классы атрибутов, вы неправильно разделяете проблемы. Есть случаи, когда используемая среда делает практически невозможным использование правильного внедрения зависимостей, и иногда мы вынуждены использовать локатор службы, но я бы определенно попытался взять истинный DI как можно дальше, прежде чем вернуться к этому. шаблон.
StriplingWarrior
1

Хорошее использование Dependency Injection обычно основано на разделении кода, который создает объекты и реальную бизнес-логику. Другими словами, я бы не хотел, чтобы моя команда часто использовала newи создавала экземпляр класса таким образом. Как только это будет сделано, нет способа легко заменить этот созданный тип на другой, поскольку вы уже указали конкретный тип.

Таким образом, есть два способа исправить это:

  1. Введите экземпляры, которые понадобятся классу. В вашем примере внедрите TestClassв свою форму Windows, чтобы он уже имел экземпляр, когда это необходимо. Когда Ninject создает экземпляр вашей формы, он автоматически создает зависимость.
  2. В тех случаях, когда вы действительно не хотите создавать экземпляр, пока он вам не нужен, вы можете внедрить фабрику в бизнес-логику. Например, вы можете добавить IKernelв свою форму Windows, а затем использовать его для создания экземпляра TestClass. В зависимости от вашего стиля, есть и другие способы сделать это (внедрение фабричного класса, фабричного делегата и т. Д.).

Это позволяет легко поменять конкретный тип TestClass, а также изменить фактическую конструкцию класса теста, не внося изменений в код, использующий класс теста.

Крис Питман
источник
1

Я не использовал Ninject, но стандартный способ создания вещи , когда вы используете IoC, чтобы сделать это с помощью 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


источник