web-dev-qa-db-fra.com

Combiner DI avec les paramètres du constructeur?

Comment combiner l'injection de constructeur avec des paramètres de constructeur "manuels"? c'est à dire.

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

Où IService doit être résolu/injecté par mon conteneur DI, et someValue doit être spécifié. Comment mélanger les deux?

44
George R

De telles constructions doivent être évitées autant que possible. Par conséquent, demandez-vous: ce paramètre est-il vraiment requis comme argument constructeur? Ou peut-on remplacer SomeObject par un objet sans état qui est réutilisé par tous ceux qui en dépendent en passant le paramètre à la méthode que vous exécutez sur l'objet?

par exemple. Au lieu de

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

utilisation

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

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

Si cela est nécessaire, optez pour une usine:

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

Aperçu: Ninject 2.4 ne nécessitera plus l'implémentation mais permettra

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

Une autre approche - l'initialisation en deux étapes (pas liée à ninject, tout framework 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;
    }
}

et utilisation:

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

Vous ne devriez vraiment pas essayer d'utiliser D.I. pour ça. Vous pouvez trouver tous les types de solutions loufoques, mais elles peuvent ne pas avoir de sens sur la route.

Notre approche consiste à créer une usine via D.I., et la méthode Create de l'usine se construirait ensuite à l'aide de la version passée dans D.I. récipient. Nous n'avons pas à utiliser ce modèle souvent, mais lorsque nous le faisons, cela rend le produit beaucoup plus propre (car il rend nos graphiques de dépendance plus petits).

3
Beep beep

Si `` somevalue '' est toujours constant, vous pouvez penser à utiliser les paramètres d'injection pendant que vous enregistrez votre type avec le conteneur comme expliqué dans le post ci-dessous

voir ici

mais si ce n'est pas vrai, il n'y a aucun moyen de spécifier une valeur de paramètre lors de la résolution d'une instance, vous pouvez penser à déplacer la 'someValue' du constructeur et à en faire une propriété de la classe.

1
TalentTuner

Dans NInject, avec lequel vous avez marqué cela, vous injectez une Factory générée automatiquement sous la forme d'un Func<parameters you wish to feed in,T>, en utilisant le FuncModule comme décrit dans cet article .

Cette approche est également disponible en autofac pour un.

Les différentes Les approches de méthode d'usine sont couvertes dans les réponses à cette question .

EDIT: NB Bien que cela puisse être amusant, veuillez utiliser la solution de @Remo Gloor (et surtout le conseil d'éviter une solution de cette nature)

1
Ruben Bartelink

J'utiliserais probablement une solution naïve à cela. Si vous connaissez la valeur de someValue lorsque vous en avez besoin, je la supprimerais du constructeur et ajouterais une propriété à votre objet afin que vous puissiez définir someValue. De cette façon, vous pouvez obtenir votre objet à partir de votre conteneur, puis définir la valeur lorsque vous avez l'objet.

Mon autre suggestion est qu'au lieu d'y accéder directement, vous créez une usine que vous pouvez utiliser pour créer un tel objet. Ensuite, vous enregistrez l'usine dans votre conteneur et utilisez l'usine pour créer votre instance. Quelque chose comme ça:

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

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

vous pourriez essayer un modèle comme ça.

PDATE: Mise à jour du code pour refléter les commentaires d'amélioration.

1
Tomas Jansson

Je ne suis pas sûr que ce soit une bonne pratique, mais cela pourrait être résolu d'une manière différente.Si vous créez une interface pour les paramètres, alors une classe qui implémente l'interface avec les valeurs dont vous avez besoin (ou aller chercher quelque part). De cette façon, DI fonctionne également avec ces paramètres.

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
 ...
0
AndersK