의존성 주입에서 "순환 의존성"을 처리하는 방법


15

제목에 "순환 종속성"이라고 표시되어 있지만 디자인이 견고 해 보이기 때문에 올바른 표현이 아닙니다.
그러나 파란색 부분이 외부 파트너에서 제공되고 주황색이 내 구현 인 다음 시나리오를 고려하십시오. 또한 하나 이상이 있다고 가정 ConcreteMain하지만 특정 것을 사용하고 싶습니다. (실제로 각 클래스에는 더 많은 종속성이 있지만 여기서 단순화하려고했습니다.)

대본

Depency Injection (Unity)을 사용 하여이 모든 것을 설명하고 싶지만 StackOverflowExceptionRunner가 ConcreteMain을 인스턴스화하려고 시도하고 ConcreteMain에 러너가 필요하기 때문에 분명히 다음 코드를 얻습니다 .

IUnityContainer ioc = new UnityContainer();
ioc.RegisterType<IMain, ConcreteMain>()
   .RegisterType<IMainCallback, Runner>();
var runner = ioc.Resolve<Runner>();

어떻게 이것을 avouid 수 있습니까? DI와 함께 사용할 수 있도록 이것을 구성하는 방법이 있습니까? 내가 지금하고있는 시나리오는 모든 것을 수동으로 설정하는 것이지만 ConcreteMain인스턴스화하는 클래스 에 큰 의존성을 부여 합니다. 이것이 내가 피하려고하는 것입니다 (구성의 Unity 등록으로).

아래의 모든 소스 코드 (매우 단순화 된 예!);

public class Program
{
    public static void Main(string[] args)
    {
        IUnityContainer ioc = new UnityContainer();
        ioc.RegisterType<IMain, ConcreteMain>()
           .RegisterType<IMainCallback, Runner>();
        var runner = ioc.Resolve<Runner>();

        Console.WriteLine("invoking runner...");
        runner.DoSomethingAwesome();

        Console.ReadLine();
    }
}

public class Runner : IMainCallback
{
    private readonly IMain mainServer;

    public Runner(IMain mainServer)
    {
        this.mainServer = mainServer;
    }

    public void DoSomethingAwesome()
    {
        Console.WriteLine("trying to do something awesome");
        mainServer.DoSomething();
    }

    public void SomethingIsDone(object something)
    {
        Console.WriteLine("hey look, something is finally done.");
    }
}

public interface IMain
{
    void DoSomething();
}

public interface IMainCallback
{
    void SomethingIsDone(object something);
}

public abstract class AbstractMain : IMain
{
    protected readonly IMainCallback callback;

    protected AbstractMain(IMainCallback callback)
    {
        this.callback = callback;
    }

    public abstract void DoSomething();
}

public class ConcreteMain : AbstractMain
{
    public ConcreteMain(IMainCallback callback) : base(callback){}

    public override void DoSomething()
    {
        Console.WriteLine("starting to do something...");
        var task = Task.Factory.StartNew(() =>{ Thread.Sleep(5000);/*very long running task*/ });
        task.ContinueWith(t => callback.SomethingIsDone(true));
    }
}

답변:


10

할 수있는 일은 ConcreteMain의 인스턴스를 IMain으로 반환하는 팩토리 MainFactory를 만드는 것입니다.

그런 다음이 팩토리를 Runner 생성자에 삽입 할 수 있습니다. 팩토리를 사용하여 Main을 만들고 inn 자체를 매개 변수로 전달하십시오.

ConcreteMain 생성자에 대한 다른 모든 종속성은 IOC를 통해 MyMainFactory로 전달되어 수동으로 구체적 생성자로 푸시 될 수 있습니다.

public class MyMainFactory
{
    MyOtherDependency _dependency;

    public MyMainFactory(MyOtherDependency dependency)
    {
        _dependency = dependency;
    }

    public IMain Create(Runner runner)
    {
        return new ConcreteMain(runner, _dependency);
    }
}

public class Runner
{
    IMain _myMain;
    public Runner(MyMainFactory factory)
    {
        _myMain = factory.Create(this)
    }
}

4

이 시나리오를 지원하는 IOC 컨테이너를 사용하십시오. 나는 AutoFac과 다른 사람들이 할 수 있다는 것을 알고 있습니다. AutoFac을 사용할 때는 종속성 중 하나에 PropertiesAutoWired = true가 있어야하며 종속성에 대한 속성을 사용해야합니다.


4

일부 IOC 컨테이너 (예 : Spring 또는 Weld)는 동적으로 생성 된 프록시를 사용하여이 문제를 해결할 수 있습니다. 프록시는 양쪽 끝에 주입되며 실제 객체는 프록시를 처음 사용할 때만 인스턴스화됩니다. 이렇게하면 두 객체가 생성자에서 서로 메소드를 호출하지 않는 한 순환 종속성은 문제가되지 않습니다 (피하기 쉽습니다).


4

Unity 3를 사용하면 이제를 주입 할 수 있습니다 Lazy<T>. 이것은 팩토리 / 오브젝트 캐시 주입과 유사합니다.

지연 종속성을 해결해야하는 ctor에서 작업하지 마십시오.

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