【问题标题】:Autofac Factories with Named Bindings具有命名绑定的 Autofac 工厂
【发布时间】:2015-02-17 10:57:56
【问题描述】:

我来自使用 Ninject 的 IoC 背景,但是,在需要在 Windows 和 Xam.Mac 之间创建可移植代码后,我选择将我的容器切换到 AutoFac(主要是因为 NancyFx 提供了官方的引导程序)。

我在 Ninject 生态系统中依赖的扩展之一是 Ninject.Extensions.Factory,通过它我可以注入一个抽象工厂来构造一个类的不同可能的具体实例。我的意思的一个例子可以在扩展文档中找到here。

我了解 Autofac 可以使用稍微不同的结构将此类工厂实现为委托,但我无法根据需要改变工厂构造参数。

首先,Autofac 可以做到这一点吗?此外,我觉得这种做法似乎将紧耦合重新引入代码库,尽管是以一种稍微迂回的方式。这种“限制”会不会是变相的祝福,打破了隐藏的反模式?

【问题讨论】:

    标签: c# inversion-of-control ninject autofac


    【解决方案1】:

    我不喜欢也从不喜欢您引用的工厂命名约定 here。在我之前的一个项目中,我们替换了部分工厂扩展来处理属性。例如,您可以有一个 IFactory 和一个方法 Foo Create([Named] name)。我们还有其他属性,例如 [MatchByType] 或 [MatchByName]。我更喜欢这种方法,因为它更冗长,并且需要更少的先前知识来理解正在发生的事情。

    我开始喜欢 Autofac 的代理工厂。但是,我最大的担忧与标准的 Ninject 工厂相同:它不是重构安全的,因为它将委托参数名称与构造函数参数名称匹配(因此我们调整 ninject 工厂以默认按类型而不是参数名称进行匹配)。为 Autofac 创建一个工厂扩展不会那么复杂 - 基本上可以从 Ninjects 实现中复制它。

    但是,对于工厂来说,最干净的方法可能如下:

    public class FooFactory : IFooFactory
    {
        private readonly IDependency1 dependency1;
        private readonly IDependency2 dependency2;
    
        public FooFactory(IDependency1 dependency1, IDependency2 dependency2)
        {
            this.dependency1 = dependency1;
            this.dependency2 = dependency2
        }
    
        public IFoo Create(IParameter someParameter)
        {
            return new Foo(this.dependency1, this.dependency2, someParameter);
        }
    }
    

    这是重构安全的。此外,如果您使用 FxCop,如果您有未使用的变量,也会收到警告。 但是,您还必须考虑这是否会为注入工厂的依赖项产生正确的“生命周期范围”。保持清洁,这可能会引入额外的工厂。或者,您可以选择在此处允许与 DI 容器更紧密地耦合,以直接从容器中检索所有依赖项。如果您打算改用 Autofac 的 ILifetimescope(或 Owned<T>),那么无论如何您都会与容器紧密耦合。

    最后,我个人选择坚持使用 Autofac 的代理工厂并使用广泛的规范测试来确定一切都按预期工作/防止不完整的重命名重构。

    编辑:今天“自动生成的工厂”又遇到了另一个问题。也不是第一次了。这是一个参数不匹配的 autofac 委托工厂。我总是觉得如果有未使用的参数,DI 容器不会警告我,这不是很好。通常这意味着代码中存在错误。 所以如果我可以选择,我想要这样的东西:

    public IFoo Create(IParameter someParameter)
    {
        return new Foo(this.dependency1, this.dependency2, someParameter);
    }
    

    我认为相当多的 DI 容器已经支持这种语法来选择构造函数。为什么不用于后期分辨率?另一种方法是拆分分辨率并绑定:

    Bind<Foo>().ToConstructor(c => new Foo(c.Inject, c.Inject, c.FromParameter);
    

    然后如果工厂缺少参数,它可能会抛出异常。 但它仍然需要某种匹配逻辑,仍然不一定是重构安全的,等等。

    【讨论】:

      【解决方案2】:

      我想真正回答您的第一个问题,我认为是“如何请求工厂进行命名注册”;-) 据我所知,您无法根据(命名)约定或属性来执行此操作。相反,您必须注册一个专门这样做的工厂委托。

      (旁注:使用 Autofac,基本功能被命名为“键控”服务,字符串类型键是一种特殊情况,称为“命名服务” - 参见 documentation)

      例子:

      // makes types FooLicious and FooMinable available as IFoo
      // similar to ninject's Bind<IFoo>().To<FooLicious>().Named("delicious");
      builder.RegisterType<FooLicious>().Named<IFoo>("delicious");
      builder.RegisterType<FooMinable>().Named<IFoo>("abominable");
      
      //factory delegate definition
      public delegate IFoo FooFactory(string name);
      
      builder.Register<FooFactory>(c => 
      {
          var context = c.Resolve<IComponentContext>();
          return name => 
          {
              return context.ResolveNamed<IFoo>(name);
          };
      });
      
      FooFactory factory = container.Resolve<FooFactory>();
      IFoo = factory("delicious"); // returns a FooDelicious
      

      注意:我真的不知道为什么必须从c 检索IComponentContext - 这本身就是IComponentContext。我想我在某个地方读过这个,但我不记得在哪里。或许根本就不需要……

      遗憾的是 Autofac 的语法需要习惯并且不像 Ninject 的语法那么直接......


      替代方案:当然您也可以直接使用 DI 容器而不是委托工厂...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-12
        • 1970-01-01
        相关资源
        最近更新 更多