如何在依赖注入中处理“循环依赖”


15

标题说“ Circular Dependency”,但这不是正确的措辞,因为对我来说,设计似乎很牢固。
但是,请考虑以下情形,其中蓝色部分由外部合作伙伴提供,橙色是我自己的实现。还假设有多个ConcreteMain,但我要使用一个特定的。(实际上,每个类都有更多的依赖关系,但是我在这里尝试简化它)

情境

我想通过Depency Injection(Unity)实例化所有这些,但是我显然可以StackOverflowException在以下代码上找到,因为Runner试图实例化ConcreteMain,而ConcreteMain需要一个Runner。

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

我该如何避免呢?有什么方法可以构造它,以便可以与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));
    }
}

Answers:


10

您可以做的是创建一个工厂MainFactory,该工厂将ConcreteMain的一个实例返回为IMain。

然后,您可以将此Factory注入到Runner构造函数中。用工厂创建Main,然后将inn本身作为参数传递。

可以通过IOC将对ConcreteMain构造函数的任何其他依赖关系传递到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中工作即可。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.