简体   繁体   English

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

[英]Combining DI with constructor parameters?

How do I combine constructor injection with "manual" constructor parameters?如何将构造函数注入与“手动”构造函数参数结合起来? ie. IE。

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

Where IService should be resolved/injected by my DI container, and someValue should be specified.我的 DI 容器应该解析/注入 IService,并且应该指定 someValue。 How do I mix the two?我如何混合两者?

Such constructs should be avoided whenever possible.应尽可能避免此类构造。 Therefore, ask yourself: is this parameter really required as constructor argument?因此,问问自己:这个参数真的需要作为构造函数的参数吗? Or can SomeObject be replaced by a stateless one which is reused by everyone that depends on it by passing the parameter to the method you execute on the object?或者可以将 SomeObject 替换为无状态的,通过将参数传递给您在对象上执行的方法,每个依赖它的人都可以重用它?

eg Instead of例如代替

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;
    }
}

use采用

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

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

If it is required go for a factory:如果需要去工厂:

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);
    }
}

Preview: Ninject 2.4 won't require the implementation anymore but allow预览:Ninject 2.4 不再需要实现,但允许

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

You really shouldn't try to use DI for this.您真的不应该尝试为此使用 DI。 You could come up with all types of wacky solutions, but they may not make sense down the road.你可以想出所有类型的古怪解决方案,但它们在未来可能没有意义。

Our approach is to create a factory via DI, and the factory's Create method would then build itself out using the passed in DI container.我们的方法是通过 DI 创建一个工厂,然后工厂的 Create 方法将使用传入的 DI 容器构建自己。 We don't have to use this pattern often, but when we do it actually makes the product much cleaner (since it makes our dependency graphs smaller).我们不必经常使用这种模式,但是当我们这样做时,实际上会使产品更干净(因为它使我们的依赖图更小)。

Another approach - initialization in two steps (not ninject related, any DI framework):另一种方法 - 分两步初始化(与 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;
    }
}

and usage:和用法:

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

I would probably use a naive solution to this.我可能会对此使用一个天真的解决方案。 If you know the value of someValue when you need it I would remove it from the constructor and add a property to your object so you can set someValue .如果您在需要时知道someValue的值,我会将其从构造函数中删除并向您的对象添加一个属性,以便您可以设置someValue This way you can get your object from your container and then set the value when you have the object.通过这种方式,您可以从容器中获取对象,然后在拥有对象时设置该值。

My other suggestion is that you instead of accessing it directly you create a factory that you can use to create such object.我的另一个建议是,您不要直接访问它,而是创建一个可用于创建此类对象的工厂。 Then you register the factory in your container and use the factory to create your instance.然后您在容器中注册工厂并使用工厂创建您的实例。 Something like this:像这样的东西:

public class SomeObjectFactory : ISomeObjectFactory
{
    private IYourService _service;
    public SomeObjectFactory(IYourService service) 
    {
        _service = service;
    }

    public ISomeObject Create(float someValue)
    {
        return new SomeObject(_service, someValue);
    }
}

you could try a pattern like that.你可以试试这样的模式。

UPDATE: Updated the code to reflect improvement comments.更新:更新了代码以反映改进意见。

I am not sure this is a good practice, but it could be solved in a different way, If you create an interface for the parameters, then a class that implements the interface with the values that you need (or fetch from somewhere).我不确定这是一个很好的做法,但可以用不同的方式解决,如果你为参数创建一个接口,那么一个类用你需要的值(或从某处获取)来实现接口。 That way DI works with those parameters as well.这样 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
 ...

If 'somevalue' is always constant then you can think of using InjectionParameters while you are register your type with the container as it explained in the below post如果 'somevalue' 始终保持不变,那么您可以在向容器注册类型时考虑使用 InjectionParameters,如下面的帖子中所述

See Here 看这里

but if that is not true, than there is no way to sepcify a parameter value while resolving a instance , you may think of moving the 'someValue' from the constructor and make it a property of the class.但如果这不是真的,那么在解析实例时无法分离参数值,您可能会考虑从构造函数中移动“someValue”并使其成为类的属性。

In NInject, which you have tagged this with, you inject an automatically-generated Factory in the form of a Func<parameters you wish to feed in,T> , using the FuncModule as described in this post .在 NInject 中,你已经标记了 this,你使用FuncModule 以Func<parameters you wish to feed in,T> want Func<parameters you wish to feed in,T>的形式注入一个自动生成的 Factory, 如本文所述

This approach is also available in autofac for one.这种方法也可以在 autofac 中使用。

The various Factory method approaches are covered in the answers to this question . 这个问题的答案涵盖了各种工厂方法方法

EDIT: NB While this may be entertaining, please use @Remo Gloor's solution (and critically the advice re avoiding a solution of this nature)编辑:注意,虽然这可能很有趣,但请使用@Remo Gloor 的解决方案(关键是建议避免使用这种性质的解决方案)

Isn't this exactly what DI\Container::make() is for?这不正是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