【问题标题】:How can I resolve circular dependencies in Funq IoC?如何解决 Funq IoC 中的循环依赖?
【发布时间】:2012-09-06 13:59:16
【问题描述】:

我有两个类需要相互引用。

class Foo
{
    public Foo(IBar bar) {}
}

class Bar
{
    public Bar(IFoo foo) {}
}

当我这样做时:

container.RegisterAutoWiredAs<Foo, IFoo>();
container.RegisterAutoWiredAs<Bar, IBar>();

当我尝试解析任一接口时,我得到一个循环依赖图,导致无限循环。有没有在 Funq 中解决这个问题的简单方法,或者您知道解决方法吗?

【问题讨论】:

  • 循环引用通常指向更大的问题。也许IBarIFoo 变得太大,需要拆分成单独的角色接口?
  • 是的,但不是我的问题的答案。
  • 循环 deps 通常是一种代码气味。解决它的一种方法是拥有一个接受/包含两者的复合依赖项 - 然后你可以传递它。
  • 您使用的是哪个 IoC 容器?
  • 我使用的是 ServiceStack 附带的 Funq 版本

标签: c# ioc-container servicestack circular-dependency funq


【解决方案1】:

您始终可以(并且在所有容器中,我会说)依赖 Lazy 作为依赖项,这将产生所需的结果。在 Funq 中:

public Bar(Lazy<IFoo> foo) ...
public Foo(Lazy<IBar> bar) ...

container.Register<IBar>(c => new Bar(c.LazyResolve<IFoo>());
container.Register<IFoo>(c => new Foo(c.LazyResolve<IBar>());

【讨论】:

    【解决方案2】:

    您的问题的答案是否定的,没有简单的方法。鉴于上面的代码,没有 Funq 就不可能构造任何一个类,因此没有理由期望 Func 能够做到。

    var foo = new Foo(/* what do I pass here? */);
    var bar = new Bar(foo);
    

    当然,如果您有另一个没有依赖关系的 IFooIBar 的实现,或者您进行了重构,这可能是可能的。

    【讨论】:

      【解决方案3】:

      一般来说,“在进行依赖注入时如何分解循环引用”这个问题的答案是:“使用属性注入”。

      class Foo
      {
          public Foo() {}
      
          // Break the dependency cycle by promoting IBar to a property.
          public IBar Bar { get; set; }
      }
      
      class Bar
      {
          public Bar(IFoo foo) {}
      }
      

      使用 Funq,我认为这将是注册此依赖项的方式。

      container.Register<IBar>(c =>
      {
          var foo = new Foo();
          var bar = new Bar(foo);
          foo.Bar = bar;
          return bar;
      });
      

      此外,我同意 Tim Rogers 的评论。当你有循环依赖时,你的设计可能有问题,你应该看看它。这并不总是错误的,但经常是。但是,您展示的代码非常抽象,我们无法对此提供任何反馈。

      【讨论】:

        【解决方案4】:

        这对我有用:

        using Funq;
        using NUnit.Framework;
        
        namespace FunqIoCyclicReferenceTest
        {
            [TestFixture]
            public class FunqIoCCyclicReferenceTest
            {
                [Test]
                public void Please_Work()
                {
                    var container = new Container();
                    container.Register<IBar>(c => new Bar());
                    container.Register<IFoo>(c => new Foo(c.Resolve<IBar>()));
        
                    var foo = container.Resolve<IFoo>();
        
                    Assert.IsNotNull(foo);
                }
            }
        
            public class Foo : IFoo
            {
                public Foo(IBar bar)
                {
                    bar.Foo = this;
                    Bar = bar;
                }
        
                public IBar Bar { get; set; }
            }
        
            public interface IBar
            {
                IFoo Foo { get; set; }
            }
        
            public interface IFoo
            {
                IBar Bar { get; set; }
            }
        
            public class Bar : IBar
            {
                public IFoo Foo { get; set; }
            }
        }
        

        编辑
        相同的想法,但在构造函数中没有副作用:

        // interfaces
        public interface IBar
        {
            IFoo Foo { get; set; }
        }
        
        public interface IFoo
        {
            IBar Bar { get; set; }
        }
        
        // implementations
        public class Foo : IFoo
        {
            public IBar Bar { get; set; }
        }    
        
        public class Bar : IBar
        {
            public IFoo Foo { get; set; }
        }
        
        // usage
        container.Register<IBar>(c => new Bar());
        container.Register<IFoo>(c => 
        {
            var bar = c.Resolve<IBar>();
            var foo = new Foo();
        
            bar.Foo = foo;
            foo.Bar = bar;
        });
        

        附言但我同意 Tim Rogers 的观​​点——循环引用是一个需要解决的问题。

        【讨论】:

        • 不,这不会有什么不同。解析任一接口仍会造成无限循环。示例:解析 IFoo 将解析 IBar,这将创建一个新 Bar 并尝试解析 IFoo,然后我们回到开始的地方等等。
        • 这仍然没有改变任何东西。当您解析 IFoo 或 IBar 时,容器将进入无限循环。
        • 当然,但是我们在 Foo 构造函数中得到了一个副作用,即 IMO 与循环依赖一样多是代码气味
        【解决方案5】:

        在容器中注册您的类型后,将容器作为静态变量提供:

        public static class ContainerHolder
        {
           public static Container Container {get;set;}
        }
        
        public class Foo : IFoo
        {
          private IBar _bar;
        
          public Foo(IBar bar)
          {
            _bar = bar;
          }
        }
        
        public class Bar : IBar
        {
          private IFoo _foo
          {
            get { return ContainerHolder.Container.Resolve<IFoo>(); }
          }
        
          public Bar()
          {
          }
        
        }
        

        【讨论】:

        • 当然,这是一种解决方法,但它的侧步是 DI 的全部要点。您只引入了一个静态依赖项,我必须在单元测试中伪造它,并确保在每次测试后恢复状态。不喜欢
        • 同意。也许您可以进行一些重构,通常首先要有循环依赖关系是不好的。也许是 Foo 和 Bar 都使用的 Baz 类?
        【解决方案6】:

        我有一个类似的场景,两个类之间的依赖关系让我意识到它们实际上应该组合成一个类。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-03-15
          • 2016-09-21
          • 2020-11-12
          相关资源
          最近更新 更多