NInject를 사용하여 팩토리를 구축하는 가장 좋은 방법은 무엇입니까?


27

MVC3에서 NInject를 사용하여 의존성 주입에 익숙합니다. MVC3 응용 프로그램에서 작업하는 동안 NInject를 사용하여 사용자 지정 Controller Creation Factory를 개발 했으므로 생성 된 모든 컨트롤러에는이 Controller Factory를 통해 종속성이 주입됩니다.

이제 Windows 응용 프로그램을 개발하기 시작했습니다. 응용 프로그램 전체 종속성 주입을 사용하고 싶습니다. 즉, 단위 테스트를 용이하게하기 위해 모든 개체는 NInject를 통해 생성되어야합니다. 생성 된 모든 객체가 NInject Factory를 통해서만 이루어져야하는지 안내해주십시오.

예를 들어, Button_Click이벤트 에 대한 모든 창에서 나는 다음과 같이 씁니다.

TestClass testClass = new TestClass()

그리고 TestClass말에 종속성을 가지고, ITest그것은 자동으로 해결해야합니다. 나는 내가 사용할 수 있다는 것을 안다.

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

그러나 객체를 만들 때 마다이 작업을 지루하게 만듭니다. 또한 개발자가 특정 방식으로 객체를 생성하도록합니다. 더 나아질 수 있습니까?

객체 생성을위한 중앙 리포지토리를 가질 수 있고 모든 객체 생성시 해당 리포지토리가 자동으로 사용됩니까?


1
안녕하세요 Pravin Patil : 좋은 질문입니다. 나는 당신이 요구하는 것을 더 명확하게하기 위해 당신의 제목을 약간 변경했습니다. 마크를 놓친 경우 자유롭게 수정하십시오.

@MarkTrapp : 적절한 제목을 주셔서 감사합니다. 나는 그 줄거리를 놓쳤다 ...
Pravin Patil

부수적으로, 프로젝트는 "NInject"가 아니라 "Ninject"라는 철자가 있습니다. En-Inject 일 수도 있지만 요즘에는 닌자 테마로 플레이하고 있습니다. :) Cf. ninject.org
Cornelius

답변:


12

클라이언트 애플리케이션의 경우 MVP (또는 MVVM)와 같은 패턴을 적용하고 양식에서 기본 ViewModel 또는 Presenter로 데이터 바인딩을 사용하는 것이 가장 좋습니다.

ViewModel의 경우 표준 생성자 주입을 사용하여 필요한 종속성을 주입 할 수 있습니다.

응용 프로그램의 Composition 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);

이 팩토리를 완전히 구현 했습니까?
Tebo

@ ColourBlend : 아니요, 그러나 InjectionFactory<T>구현 한 다른 인터페이스를 제거하면 제공된 코드가 정상적으로 작동합니다. 특히 문제가있는 것이 있습니까?
StriplingWarrior

나는 이미 그것을 구현했다. 나는 단지 클래스 내에 다른 흥미로운 것들이 있는지 알고 싶다.
Tebo

@ 테보 : 방금 전달할 수있는 팩토리와 같은 다른 DI 관련 인터페이스 TypeType구현 했지만 그 구현을 위해 수화 된 객체 가 주어진 일반 유형을 확장하거나 보장 할 수 있습니다 . 너무 특별한 것은 없습니다.
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);
    }
}

이를 통해 자동 배선 휴리스틱에 모듈에 설정된 규칙에 맞는 형식으로 모든 속성을 재귀 적으로 주입하려고합니다.


아주 좋은 해결책 ..... 시험해 볼게요.
Pravin Patil

10
: 그 웅장 나쁜 생각 서비스 로케이터,의 blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
마크 시만

2
@MarkSeemann : 서비스 로케이터는 최상위 객체를 최대한 멀리 연결하는 대신 어디에서나 액세스 할 수있는 나쁜 아이디어입니다. "이러한 경우에는 컴포지션 루트를 각 개체 (예 : 페이지)로 옮기고 DI 컨테이너가 종속성을 연결하도록하는 것 외에는 다른 방법이 없습니다. 다음과 같이 보일 수 있습니다. 서비스 로케이터 안티 패턴이지만 컨테이너 사용량을 절대 최소로 유지하기 때문이 아닙니다. " (편집 : 잠깐 만요, 마크입니다! 차이점은 무엇입니까?)
pdr

1
차이점은 모든 클래스에서 Singleton Service Locator를 사용할 수 있도록하는 대신 나머지 코드베이스를 Composer에서 보호 할 수 있다는 것입니다.
Mark Seemann

2
@ pdr : 내 경험에 따르면 서비스를 속성 클래스와 같은 것에 주입하려고하면 우려를 올바르게 분리하지 못합니다. 사용중인 프레임 워크가 적절한 의존성 주입을 사용하는 것이 실제로 불가능한 경우가 있으며 때로는 서비스 로케이터를 사용해야하는 경우가 있지만이로 되돌리기 전에 가능한 한 진정한 DI를 가져 가려고합니다. 무늬.
StriplingWarrior

1

Dependency Injection을 잘 사용하려면 일반적으로 객체와 실제 비즈니스 로직을 생성하는 코드를 분리해야합니다. 다시 말해, 팀이 그런 식으로 클래스의 인스턴스 를 자주 사용 하고 생성 하기를 원하지 않습니다new . 완료된 후에는 이미 구체적인 유형을 지정 했으므로 생성 된 유형을 다른 유형으로 쉽게 교체 할 수있는 방법이 없습니다.

따라서이 문제를 해결하는 두 가지 방법이 있습니다.

  1. 수업에 필요한 인스턴스를 주입하십시오. 예를 들어, TestClass필요할 때 이미 인스턴스가 있도록 Windows Form에 a 를 삽입 하십시오. Ninject가 양식을 인스턴스화하면 종속성이 자동으로 작성됩니다.
  2. 당신이 정말로 경우 하지 않습니다 당신이 그것을 필요로 할 때까지 인스턴스를 만들려면, 당신은 비즈니스 로직에 공장을 주입 할 수 있습니다. 예를 들어 IKernelWindows Form에 a 를 삽입 한 다음이를 사용하여 인스턴스화 할 수 TestClass있습니다. 스타일에 따라 다른 방법으로도이를 수행 할 수 있습니다 (팩토리 클래스, 팩토리 델리게이트 주입 등).

이렇게하면 테스트 클래스를 사용 하는 코드를 실제로 수정하지 않고도 구체적인 유형의 TestClass를 쉽게 교체하고 테스트 클래스의 실제 구성을 쉽게 수정할 수 있습니다.


1

Ninject를 사용하지는 않았지만 IoC를 사용할 때 물건을 만드는 표준 방법은 Func<T>어디 T에서 만들려는 객체 유형을 통해 수행 하는 것입니다. 따라서 T1객체가 유형의 객체를 생성 해야하는 경우 T2의 생성자 T1는 유형의 매개 변수를 가져야 Func<T1>하며 필드의 / 속성으로 저장됩니다 T2. 지금 당신은 타입의 객체 생성 할 때 T2T1인보을 Func.

이는 IoC 프레임 워크에서 완전히 분리되어 IoC 사고 방식으로 코딩하는 올바른 방법입니다.

이 작업의 단점은 수동으로 와이어를 연결해야 할 때 성가 시거나 Func생성자가 일부 매개 변수를 요구할 때 성 가실 수 있으므로 IoC가 자동으로 연결할 수 없다는 Func것입니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.