116 votes

Existe-t-il une alternative à l'injection bâtarde ? (alias l'injection du pauvre via le constructeur par défaut)

Je suis le plus souvent tenté d'utiliser l'"injection bâtarde" dans quelques cas. Quand j'ai un constructeur d'injection de dépendances "correct" :

public class ThingMaker {
    ...
    public ThingMaker(IThingSource source){
        _source = source;
    }

Mais alors, pour les cours que j'ai l'intention de faire en tant que APIs publiques (classes qui seront utilisées par d'autres équipes de développement), je ne trouve jamais de meilleure option que d'écrire un constructeur "bâtard" par défaut avec la dépendance la plus probable :

    public ThingMaker() : this(new DefaultThingSource()) {} 
    ...
}

L'inconvénient évident ici est que cela crée une dépendance statique sur DefaultThingSource ; idéalement, il n'y aurait pas une telle dépendance, et le consommateur injecterait toujours l'IThingSource qu'il veut. Cependant, c'est trop difficile à utiliser ; les consommateurs veulent ouvrir un ThingMaker et se mettre au travail pour créer des choses, puis quelques mois plus tard injecter quelque chose d'autre quand le besoin s'en fait sentir. À mon avis, il ne reste que quelques options :

  1. Omettre le constructeur bâtard ; forcer le consommateur de ThingMaker à comprendre IThingSource, comprendre comment ThingMaker interagit avec IThingSource, trouver ou écrire une classe concrète, et ensuite injecter une instance dans leur appel de constructeur.
  2. Omettre le constructeur bâtard et fournir une fabrique, un conteneur ou une autre classe/méthode d'amorçage séparée ; faire comprendre au consommateur qu'il n'a pas besoin d'écrire son propre IThingSource ; forcer le consommateur de ThingMaker à trouver et à comprendre la fabrique ou l'amorceur et à l'utiliser.
  3. Garder le constructeur bâtard, permettant au consommateur de "new up" un objet et de l'exécuter, et faire face à la dépendance statique optionnelle sur DefaultThingSource.

Bon sang, le numéro 3 a l'air séduisant. Y a-t-il une autre option, meilleure ? #1 ou #2 ne semblent pas en valoir la peine.

60voto

Mark Seemann Points 102767

D'après ce que je comprends, cette question porte sur la manière d'exposer une API à couplage lâche avec des valeurs par défaut appropriées. Dans ce cas, vous pouvez avoir une bonne Défaut local Dans ce cas, la dépendance peut être considérée comme facultative. Une façon de traiter les dépendances optionnelles est d'utiliser Injection de propriété au lieu de Injection de constructeur - en fait, c'est une sorte de scénario d'affiche pour l'injection de propriété.

Cependant, le véritable danger de l'injection de bâtard est lorsque la valeur par défaut est un Défaut étranger car cela signifierait que le constructeur par défaut entraîne un couplage indésirable avec l'assemblage qui implémente le défaut. Cependant, si je comprends bien la question, le constructeur par défaut prévu proviendrait du même assemblage, auquel cas je ne vois pas de danger particulier.

Dans tous les cas, vous pouvez également envisager une façade, comme décrit dans l'une de mes réponses précédentes : Bibliothèque "amicale" de Dependency Inject (DI)

D'ailleurs, la terminologie utilisée ici est basée sur le langage des modèles de l'Institut européen de l'environnement. mon livre .

32voto

Steve Jackson Points 903

Ma contrepartie est un tour sur @BrokenGlass :

1) Le constructeur unique est un constructeur paramétré

2) Utilisez la méthode factory pour créer un ThingMaker et transmettez cette source par défaut.

public class ThingMaker {
  public ThingMaker(IThingSource source){
    _source = source;
  }

  public static ThingMaker CreateDefault() {
    return new ThingMaker(new DefaultThingSource());
  }
}

Évidemment, cela n'élimine pas votre dépendance, mais cela rend plus clair pour moi que cet objet a des dépendances dans lesquelles un appelant peut plonger s'il le souhaite. Vous pouvez rendre cette méthode d'usine encore plus explicite si vous le souhaitez (CreateThingMakerWithDefaultThingSource) si cela aide à la compréhension. Je préfère cette méthode à celle de la fabrique IThingSource car elle continue à favoriser la composition. Vous pouvez également ajouter une nouvelle méthode factory lorsque le DefaultThingSource est obsolète et avoir un moyen clair de trouver tout le code utilisant le DefaultThingSource et le marquer pour être mis à jour.

Vous avez couvert les possibilités dans votre question. Une classe d'usine ailleurs pour des raisons de commodité ou une certaine commodité au sein de la classe elle-même. La seule autre option peu attrayante serait de se baser sur la réflexion, ce qui cacherait encore plus la dépendance.

8voto

BrokenGlass Points 91618

Une alternative est d'avoir un méthode d'usine CreateThingSource() dans votre ThingMaker qui crée la dépendance pour vous.

Pour les essais ou si vous avez besoin d'un autre type de IThingSource vous devriez alors créer une sous-classe de ThingMaker et de passer outre CreateThingSource() pour retourner le type de béton que vous voulez. Évidemment, cette approche ne vaut la peine que si vous avez principalement besoin de pouvoir injecter la dépendance pour les tests, mais pour la plupart/toutes les autres utilisations, vous n'avez pas besoin d'un autre fichier de type IThingSource

6voto

Fernando Points 552

Je vote pour le n°3. Vous rendrez votre vie - et celle des autres développeurs - plus facile.

6voto

JCallico Points 832

Si vous devez avoir une dépendance "par défaut", également connue sous le nom d'injection de dépendance du pauvre, vous devez alors initialiser et "câbler" la dépendance quelque part.

Je vais garder les deux constructeurs mais avoir une usine juste pour l'initialisation.

public class ThingMaker
{
    private IThingSource _source;

    public ThingMaker(IThingSource source)
    {
        _source = source;
    }

    public ThingMaker() : this(ThingFactory.Current.CreateThingSource())
    {
    }
}

Maintenant, dans la fabrique, créez l'instance par défaut et autorisez la méthode à être surchargée :

public class ThingFactory
{
    public virtual IThingSource CreateThingSource()
    {
        return new DefaultThingSource();
    }
}

Mise à jour :

Pourquoi utiliser deux constructeurs : Deux constructeurs montrent clairement comment la classe est destinée à être utilisée. Le constructeur sans paramètre indique : il suffit de créer une instance et la classe s'acquittera de toutes ses responsabilités. Le second constructeur indique que la classe dépend de IThingSource et fournit un moyen d'utiliser une implémentation différente de celle par défaut.

Pourquoi utiliser une usine : 1- Discipline : La création de nouvelles instances ne devrait pas faire partie des responsabilités de cette classe, une classe usine est plus appropriée. 2- DRY : Imaginez que dans la même API d'autres classes dépendent aussi de IThingSource et font la même chose. Surchargez une fois la méthode de fabrique retournant IThingSource et toutes les classes dans votre API commencent automatiquement à utiliser la nouvelle instance.

Je ne vois pas de problème à coupler ThingMaker à une implémentation par défaut de IThingSource tant que cette implémentation a un sens pour l'API dans son ensemble et que vous fournissez des moyens de remplacer cette dépendance pour les tests et les extensions.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X