【问题标题】:Combining DI with constructor parameters?将 DI 与构造函数参数相结合?
【发布时间】:2011-10-19 15:15:45
【问题描述】:

如何将构造函数注入与“手动”构造函数参数结合起来?即。

public class SomeObject
{
    public SomeObject(IService service, float someValue)
    {
    }
}

我的 DI 容器应该在哪里解析/注入 IService,并且应该指定 someValue。如何混合两者?

【问题讨论】:

  • 手动构造函数参数是指手动构造类而不是 DI 还是指传入参数的 DI 容器。如果是前者,那么你可以只做一个构造函数重载吗?
  • 无论如何我总是需要 IService,所以我认为我不能只是重载,没有依赖关系,它神奇地使用了完整的构造函数(除非我使用 ServiceLocator - 太糟糕了!)。跨度>

标签: c# ninject dependency-management


【解决方案1】:

应尽可能避免此类结构。因此,问问自己:这个参数真的需要作为构造函数参数吗?或者是否可以通过将参数传递给您在对象上执行的方法来将 SomeObject 替换为依赖于它的每个人都可以重用的无状态对象?

例如而不是

public class SomeObject
{
    private float someValue
    public SomeObject(IService service, float someValue)
    {
        this.someValue = someValue
    }

    public float Do(float x)
    {
        return this.Service.Get(this.someValue) * x;
    }
}

使用

public class SomeObject
{
    public SomeObject(IService service)
    {
    }

    public float Do(float x, float someValue)
    {
        return this.Service.Get(someValue) * x;
    }
}

如果需要去工厂:

public interface ISomeObjectFactory
{
    ISomeObject CreateSomeObject(float someValue);
}

public class SomeObjectFactory : ISomeObjectFactory
{
    private IKernel kernel;
    public SomeObjectFactory(IKernel kernel) 
    {
        this.Kernel = kernel;
    }

    public ISomeObject Create(float someValue)
    {
        return this.kernel.Get<ISomeObject>(WithConstructorArgument("someValue", someValue);
    }
}

预览: Ninject 2.4 不再需要实现,但允许

kernel.Bind<ISomeObjectFactory>().ToFactory();  // or maybe .AsFactory();

【讨论】:

  • +1 绝对避免是正确的答案,编辑我的答案同意。我知道它会隐藏一点,但正如之前在其他地方所介绍的那样,考虑使用 Func ctor arg 表达您的 SomeObjectFactory 而不是直接使用内核
  • @Ruben Bartelink:我刚刚成功了这个版本,因为我认为它会是 Ninject 2.4 的首选版本。工厂将由内核自动生成。 Func 变体也将得到支持,缺点是 Func 不提供有关参数的信息并使参数匹配更加困难。因此,我不再坚持使用 Func 变体,以便能够通过简单地删除工厂实现并更改绑定来进行更新。但我也会把工厂放在引导程序中,而界面放在消费者旁边。
  • 感谢您解释 Func 注入的微妙之处 - 没想到这一点。我只是要指出,在您对 Factory [that 2.4 will autogen] 的手工实现中,您在内核上采用了一个 ctor dep,可以通过采用 Func 来删除它。现在考虑一下,这样做会使答案变得不那么清晰,并且不会映射到 ToFactory 将要做什么。所以忘记我说的话,谢谢你的解释!它是 su_gg_est :P 顺便说一句,您将方法名称输入错误为 Create(不是 CreateSomeObject)。我个人会优先使用 Func,因为我生活在一个被混淆的世界中......
  • 虽然喜欢这些想法 - 应该允许越来越少的容器特定的垃圾在引导程序之外。我仍然不确定我是否喜欢允许 IKernel 在没有显式绑定和/或 Bind 表达式上的 WithKernelOption() 的情况下可解析的默认行为,但是我没有制作很多工厂类,所以我可能是错的跨度>
  • 如果第二个是运行时实例怎么办。它将通过SomeObject 类共享。在这种情况下,将它放在构造函数中更有意义。那么这是否意味着SomeObject 是一个运行时类型,不应该放在 DI 容器中?
【解决方案2】:

你真的不应该尝试使用 D.I.为了这。你可以想出所有类型的古怪解决方案,但它们在未来可能没有意义。

我们的方法是通过 D.I. 创建一个工厂,然后工厂的 Create 方法将使用传入的 D.I. 构建自己。容器。我们不必经常使用这种模式,但是当我们这样做时,它实际上会使产品更干净(因为它使我们的依赖图更小)。

【讨论】:

    【解决方案3】:

    另一种方法 - 分两步初始化(与 ninject 无关,任何 DI 框架):

    public class SomeObject
    {
        private readonly IService _service;
    
        public SomeObject(IService service)
        {
            // constructor only captures dependencies
            _service = service;
        }
    
        public SomeObject Load(float someValue)
        {
            // real initialization goes here
            // ....
    
            // you can make this method return no value
            // but this makes it more convienient to use
            return this;
        }
    }
    

    及用法:

    public static class TestClass
    {
        public static void TestMethod(IService service)
        {
            //var someObject = new SomeObject(service, 5f);
            var someObject = new SomeObject(service).Load(5f);
        }
    }
    

    【讨论】:

    • 忘记调用Load方法是不安全的。
    • 它确实被认为是一种 OOP 反模式。 medium.com/@kooliahmd/…
    • 可能是这样,但是您提供的链接(medium.com)太糟糕了,我希望没有人从中吸取教训。
    【解决方案4】:

    我可能会使用一个幼稚的解决方案来解决这个问题。如果您在需要时知道someValue 的值,我会从构造函数中删除它并向您的对象添加一个属性,以便您可以设置someValue。这样,您可以从容器中获取对象,然后在拥有对象时设置值。

    我的另一个建议是,您不要直接访问它,而是创建一个可用于创建此类对象的工厂。然后在容器中注册工厂并使用工厂创建实例。像这样的:

    public class SomeObjectFactory : ISomeObjectFactory
    {
        private IYourService _service;
        public SomeObjectFactory(IYourService service) 
        {
            _service = service;
        }
    
        public ISomeObject Create(float someValue)
        {
            return new SomeObject(_service, someValue);
        }
    }
    

    你可以试试这样的模式。

    更新:更新了代码以反映改进的 cmets。

    【讨论】:

    • 您似乎对您在SomeObjectFactory 中选择的容器有很强的依赖关系。这不被广泛推荐。相反,将IYourService 实例注入SomeObjectFactory 构造函数,并让容器解决组合根中的依赖关系。
    • 当然,我的代码只是一个大纲,并不是最终的实现。关键是他应该改用工厂。但我根据我认为你的意思更新了代码。
    【解决方案5】:

    我不确定这是一个好习惯,但它可以通过不同的方式解决,如果您为参数创建一个接口,那么一个类会使用您需要的值(或从某个地方获取)实现该接口)。这样 DI 也可以使用这些参数。

    interface ISomeParameters
    {
      public float SomeValue { get; set; }
    }
    
    class SomeParameters : ISomeParameters
    {
     public float SomeValue{ get; set; } = 42.0;
    }
    
    services.AddSingleton(ISomeParameters, SomeParameters)
    
    public MyService(IService service, ISomeParameters someParameters)
    {
      someParameters.SomeValue
     ...
    

    【讨论】:

    • 这似乎是个好主意。但是,如果这是一种反模式或可接受的模式,其他人可以加入吗?
    • 如有任何反馈,我将不胜感激。
    【解决方案6】:

    如果 'somevalue' 始终保持不变,那么您可以考虑在向容器注册类型时使用 InjectionParameters,如下文所述

    See Here

    但如果这不是真的,那么在解析实例时没有办法对参数值进行区分,您可能会考虑将“someValue”从构造函数中移出并使其成为类的属性。

    【讨论】:

      【解决方案7】:

      在您已标记的 NInject 中,您使用FuncModule as described in this postFunc&lt;parameters you wish to feed in,T&gt; 的形式注入一个自动生成的工厂。

      这种方法在 autofac 中也可用。

      各种Factory method approaches are covered in the answers to this question

      编辑:注意虽然这可能很有趣,但请使用@Remo Gloor 的解决方案(并且严格地建议避免这种性质的解决方案)

      【讨论】:

        猜你喜欢
        • 2018-04-04
        • 1970-01-01
        • 1970-01-01
        • 2016-09-12
        • 2016-07-26
        • 1970-01-01
        • 2018-05-30
        • 2014-03-10
        • 1970-01-01
        相关资源
        最近更新 更多