繁体   English   中英

将 DI 与构造函数参数结合起来?

[英]Combining DI with constructor parameters?

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

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

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

应尽可能避免此类构造。 因此,问问自己:这个参数真的需要作为构造函数的参数吗? 或者可以将 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();

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

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

另一种方法 - 分两步初始化(与 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);
    }
}

我可能会对此使用一个天真的解决方案。 如果您在需要时知道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);
    }
}

你可以试试这样的模式。

更新:更新了代码以反映改进意见。

我不确定这是一个很好的做法,但可以用不同的方式解决,如果你为参数创建一个接口,那么一个类用你需要的值(或从某处获取)来实现接口。 这样 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
 ...

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

看这里

但如果这不是真的,那么在解析实例时无法分离参数值,您可能会考虑从构造函数中移动“someValue”并使其成为类的属性。

在 NInject 中,你已经标记了 this,你使用FuncModule 以Func<parameters you wish to feed in,T> want Func<parameters you wish to feed in,T>的形式注入一个自动生成的 Factory, 如本文所述

这种方法也可以在 autofac 中使用。

这个问题的答案涵盖了各种工厂方法方法

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

这不正是DI\Container::make()的用途吗?

$object = $container->make(SomeObject::class, ['someValue' => 0.1]);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM