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


147

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. 그러나 내가 원하는 것은이 인터페이스에 특정 시간 초기화 매개 변수가 필요하다는 것입니다. 이 정보로 인터페이스에 주석을 달고 (속성?) 객체를 만들 때 프레임 워크에 전달하는 방법이 있습니까?

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


9
DI 컨테이너를 사용할 시점이 없습니다. 의존성은 당신을 위해 해결되어야합니다.
Pierreten

필요한 매개 변수를 어디서 얻습니까? (설정 파일, db, ??)
Jaime

runTimeParam사용자 입력에 따라 런타임에 결정되는 종속성입니다. 이것에 대한 대안이 그것을 두 개의 인터페이스, 즉 하나는 초기화를위한 인터페이스와 다른 하나는 값을 저장하기위한 인터페이스로 분리되어야 하는가?
Igor Zevaka 2009

IoC의 종속성은 일반적으로 IoC 초기화 단계에서 결정될 수있는 다른 참조 유형 클래스 또는 객체에 대한 종속성을 나타냅니다. 클래스가 작동하기 위해 약간의 값이 필요한 경우 클래스의 Initialize () 메소드가 유용합니다.

앱에이 접근법을 적용 할 수있는 100 개의 클래스가 있다고 상상해보십시오. 그런 다음 클래스에 대해 100 개의 팩토리 클래스 + 100 개의 인터페이스를 추가로 만들어야하며 Initialize () 메서드를 사용하는 경우 벗어날 수 있습니다.

답변:


276

특정 의존성을 구성하기 위해 런타임 값이 필요한 곳이면 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인스턴스를 올바르게 등록하면 인스턴스 를 자동으로 연결할 수 있습니다 .


13
문제는 초기화와 같은 메소드가 API의 일부이지만 생성자는 그렇지 않다는 것입니다. blog.ploeh.dk/2011/02/28/InterfacesAreAccessModifiers.aspx
Mark Seemann

13
또한, 초기화 방법은 시간적 커플 링을 나타냅니다 : blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling.aspx
마크 시만

2
@Darlene 내 책의 8.3.6 섹션에 설명 된대로 느리게 초기화 된 Decorator를 사용할 수 있습니다 . 또한 프레젠테이션 Big Object Graphs Up Front 와 비슷한 예제를 제공합니다 .
Mark Seemann

2
@Mark 공장에서 MyIntf구현을 생성하는 데 필요한 것 이상이 필요한 경우 runTimeParam(읽기 : IoC로 해결하려는 다른 서비스), 여전히 공장에서 이러한 종속성을 해결해야합니다. 나는이 의존성을 공장의 생성자 로 전달하여 이것을 해결 하는 @PhilSandler의 답변을 좋아합니다 .
Jeff

2
또한 훌륭한 것들이지만 이 다른 질문에 대한 당신의 대답 은 실제로 내 요점을 얻었습니다.
Jeff

15

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

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

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


5

또한 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의 프로젝트 사이트에 더 많은 문서가 있습니다.


1

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

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

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

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


고마워 나는 실제로 DI 프레임 워크를 평가하고 있으며 NInject는 다음 프레임 워크가 될 것입니다.
Igor Zevaka 2009


1

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

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.


13
이것은 Leaky Abstraction 인 Initialize 방법에 의존하기 때문에 특히 좋은 해결책은 아닙니다. Btw, 이것은 서비스 로케이터는 아니지만 인터페이스 주입과 비슷합니다. 어쨌든 더 나은 솔루션에 대한 내 대답을 참조하십시오.
Mark Seemann
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.