[dependency-injection] DI 컨테이너를 통해 생성 된 객체를 초기화하는 패턴이 있습니까?

Unity가 객체 생성을 관리하도록 노력하고 있으며 런타임까지 알려지지 않은 초기화 매개 변수를 갖고 싶습니다.

현재 내가 생각할 수있는 유일한 방법은 인터페이스에 Init 메소드를 사용하는 것입니다.

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}

그런 다음 (Unity에서) 사용하려면 다음과 같이하십시오.

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");

이 시나리오에서 runTimeParam매개 변수는 사용자 입력에 따라 런타임에 결정됩니다. 여기서 사소한 경우는 단순히 값을 반환 runTimeParam하지만 실제로는 매개 변수가 파일 이름과 같으며 initialize 메소드가 파일과 관련이 있습니다.

이로 인해 여러 가지 문제가 발생합니다. 즉 Initialize, 인터페이스 에서 메소드를 사용할 수 있으며 여러 번 호출 할 수 있습니다. 구현에서 플래그를 설정하고 반복 호출에 대한 예외를 던지는 Initialize것은 어색한 것처럼 보입니다.

인터페이스를 해결하는 시점에서의 구현에 대해 알고 싶지 않습니다 IMyIntf. 그러나 내가 원하는 것은이 인터페이스에 특정 시간 초기화 매개 변수가 필요하다는 것입니다. 이 정보로 인터페이스에 주석을 달고 (속성?) 객체를 만들 때 프레임 워크에 전달하는 방법이 있습니까?

편집 : 인터페이스를 조금 더 설명했습니다.



답변

특정 의존성을 구성하기 위해 런타임 값이 필요한 곳이면 Abstract Factory 가 솔루션입니다.

인터페이스에서 메소드를 초기화하면 새는 추상화 냄새가납니다 .

귀하의 경우 IMyIntf인터페이스를 구현하려는 의도가 아니라 사용 방법대한 인터페이스를 모델링해야한다고 말하고 싶습니다 . 그것은 구현 세부 사항입니다.

따라서 인터페이스는 다음과 같아야합니다.

public interface IMyIntf
{
    string RunTimeParam { get; }
}

이제 추상 팩토리를 정의하십시오.

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}

이제 다음 과 같은 IMyIntfFactory구체적인 인스턴스를 만드는 구체적인 구현을 만들 수 있습니다 IMyIntf.

public class MyIntf : IMyIntf
{
    private readonly string runTimeParam;

    public MyIntf(string runTimeParam)
    {
        if(runTimeParam == null)
        {
            throw new ArgumentNullException("runTimeParam");
        }

        this.runTimeParam = runTimeParam;
    }

    public string RunTimeParam
    {
        get { return this.runTimeParam; }
    }
}

이것이 키워드 를 사용 하여 클래스의 불변보호 하는 방법에 주목하십시오 readonly. 냄새가 나지 않습니다. 초기화 방법이 필요합니다.

IMyIntfFactory구현이 단순하게 할 수있다 :

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}

IMyIntf인스턴스 가 필요한 모든 소비자에서 Constructor Injection을IMyIntfFactory 통해 요청 하여 종속성을 가져 옵니다 .

염분 가치가있는 DI 컨테이너는 IMyIntfFactory인스턴스를 올바르게 등록하면 인스턴스 를 자동으로 연결할 수 있습니다 .


답변

일반적으로이 상황이 발생하면 디자인을 다시 방문하여 상태 저장 / 데이터 객체를 순수한 서비스와 혼합하고 있는지 확인해야합니다. 대부분의 경우에,이 두 가지 유형의 객체를 별도로 유지하려고합니다.

생성자에 전달 된 컨텍스트 특정 매개 변수가 필요한 경우 생성자를 통해 서비스 종속성을 해결하는 팩토리를 만들고 런타임 매개 변수를 Create () 메서드의 매개 변수 (또는 Generate ( ), Build () 또는 팩토리 메소드 이름).

setter 또는 Initialize () 메소드를 갖는 것은 일반적으로 나쁜 디자인 으로 간주됩니다. 호출하기 위해 “기억”하고 구현 상태를 너무 많이 열지 않도록해야합니다 (예 : 누군가가 -호출 초기화 또는 세터?).


답변

또한 Model 객체를 기반으로 ViewModel 객체를 동적으로 생성하는 환경 에서이 상황을 몇 번 겪었습니다 (이 다른 Stackoverflow 게시물에 의해 실제로 잘 설명되어 있음) .

인터페이스를 기반으로 팩토리를 동적으로 만들 수 있는 Ninject 확장 기능 이 마음에 들었습니다 .


Bind<IMyFactory>().ToFactory();

Unity 에서 직접 비슷한 기능을 찾을 수 없었습니다 . 그래서 IUnityContainer 에 대한 내 자신의 확장 기능을 작성하여 하나의 유형 계층 구조에서 다른 유형 계층 구조로 본질적으로 매핑되는 기존 객체의 데이터를 기반으로 새 객체를 생성하는 팩토리를 등록 할 수 있습니다 : UnityMappingFactory @ GitHub

단순성과 가독성을 목표로 개별 팩토리 클래스 또는 인터페이스 (실시간 보호기)를 선언하지 않고 매핑을 직접 지정할 수있는 확장 기능을 사용했습니다. 정상적인 부트 스트랩 과정에서 클래스를 등록하는 바로 매핑을 추가하면됩니다.

//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();

//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();

그런 다음 CI 생성자에서 매핑 팩토리 인터페이스를 선언하고 Create () 메서드를 사용 하십시오 .

public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }

public TextWidgetViewModel(ITextWidget widget) { }

public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
    IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
    foreach (IWidget w in data.Widgets)
        children.Add(factory.Create(w));
}

추가 보너스로, 맵핑 된 클래스의 생성자에 대한 추가 종속성도 오브젝트 작성 중에 해결됩니다.

분명히, 이것은 모든 문제를 해결하지는 못하지만 지금까지 나에게 꽤 잘 봉사하여 공유해야한다고 생각했습니다. GitHub의 프로젝트 사이트에 더 많은 문서가 있습니다.


답변

특정 Unity 용어로 대답 할 수는 없지만 의존성 주입에 대해 배우는 것처럼 들립니다. 그렇다면 Ninject에 대한 간단하고 명확하며 정보가 가득한 사용 설명서 를 읽어 보시기 바랍니다. .

DI를 사용할 때 사용할 수있는 다양한 옵션과 그 과정에서 직면 할 특정 문제를 설명하는 방법을 안내합니다. 귀하의 경우, DI 컨테이너를 사용하여 객체를 인스턴스화하고 해당 객체가 생성자를 통해 각 종속성에 대한 참조를 얻도록 할 것입니다.

연습에서는 속성을 사용하여 메서드, 속성 및 매개 변수에 주석을 달아 런타임에 구분하는 방법도 자세히 설명합니다.

Ninject를 사용하지 않더라도이 연습에서는 사용자의 목적에 맞는 기능의 개념과 용어를 제공하며 해당 지식을 Unity 또는 기타 DI 프레임 워크에 매핑 할 수 있어야합니다 (또는 Ninject를 사용해 보도록 설득해야합니다). .


답변

나는 그것을 해결했다고 생각하고 오히려 건전한 느낌이 들기 때문에 반쯤 맞아야합니다 :))

IMyIntf“getter”와 “setter”인터페이스로 나 split 습니다. 그래서:

interface IMyIntf {
  string RunTimeParam { get; }
}


interface IMyIntfSetter {
  void Initialize(string runTimeParam);
  IMyIntf MyIntf {get; }
}

그런 다음 구현 :

class MyIntfImpl : IMyIntf, IMyIntfSetter {
  string _runTimeParam;

  void Initialize(string runTimeParam) {
    _runTimeParam = runTimeParam;
  }

  string RunTimeParam { get; }

  IMyIntf MyIntf {get {return this;} }
}

//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;

IMyIntfSetter.Initialize()여전히 여러 번 호출 할 수 있지만 Service Locator 패러다임의 비트를 사용하여 IMyIntfSetter거의 내부 인터페이스와 매우 유사 하게 마무리 할 수 ​​있습니다 IMyIntf.


답변