[C#] Gibt es eine Alternative zur Bastard-Injektion? (AKA Armer Mann Injektion über Standardkonstruktor)


Answers

Mein Kompromiss ist ein Spin auf @BrokenGlass:

1) Sole Konstruktor ist parametrisierter Konstruktor

2) Verwenden Sie die Factory-Methode, um einen ThingMaker zu erstellen und diese Standardquelle zu übergeben.

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

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

Offensichtlich beseitigt dies nicht Ihre Abhängigkeit, aber es macht mir klarer, dass dieses Objekt Abhängigkeiten hat, in die ein Anrufer tief eintauchen kann, wenn sie es wollen. Sie können diese Factory-Methode sogar noch expliziter machen, wenn Sie das möchten (CreateThingMakerWithDefaultThingSource), wenn das hilft, sie zu verstehen. Ich ziehe es vor, die IThingSource-Factory-Methode zu überschreiben, da sie weiterhin die Komposition bevorzugt. Sie können auch eine neue Factory-Methode hinzufügen, wenn die DefaultThingSource veraltet ist und eine klare Möglichkeit zum Suchen des gesamten Codes mit der DefaultThingSource bietet, und diese für das Upgrade markieren.

Du hast die Möglichkeiten in deiner Frage abgedeckt. Werksklasse anderswo für Bequemlichkeit oder etwas Bequemlichkeit innerhalb der Klasse selbst. Die einzige andere unattraktive Option wäre die Reflexion, die die Abhängigkeit noch weiter verschleiert.

Question

Ich bin meistens versucht, in einigen Fällen "Bastard-Injektion" zu verwenden. Wenn ich einen "richtigen" Konstruktor für die Abhängigkeitsinjektion habe:

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

Aber für Klassen, die ich als öffentliche APIs (Klassen, die andere Entwicklungsteams verwenden) beabsichtigen, kann ich nie eine bessere Option finden, als einen Standard-Bastard-Konstruktor mit der wahrscheinlichsten benötigten Abhängigkeit zu schreiben:

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

Der offensichtliche Nachteil besteht darin, dass dies eine statische Abhängigkeit von DefaultThingSource erzeugt; Im Idealfall würde es keine solche Abhängigkeit geben, und der Verbraucher würde immer die von ihm gewünschte IThingSource injizieren. Dies ist jedoch zu schwer zu verwenden; Die Verbraucher wollen einen ThingMaker neu aufstellen und an die Arbeit gehen, Dinge zu machen, und Monate später etwas anderes injizieren, wenn es nötig ist. Dies lässt meiner Meinung nach nur einige Optionen übrig:

  1. Entferne den Bastardkonstruktor; Zwingen Sie den Kunden von ThingMaker dazu, IThingSource zu verstehen, zu verstehen, wie ThingMaker mit IThingSource interagiert, eine konkrete Klasse zu finden oder zu schreiben und dann eine Instanz in ihren Konstruktoraufruf zu injizieren.
  2. Entfernen Sie den Bastardkonstruktor und stellen Sie eine separate Factory-, Container- oder andere Bootstrapping-Klasse / -Methode bereit; Irgendwie sollte der Verbraucher verstehen, dass er keine eigene IThingSource schreiben muss; Zwingen Sie den Verbraucher von ThingMaker dazu, den Fabrik- oder Bootstrapper zu finden und zu verstehen und ihn zu benutzen.
  3. Behalten Sie den Bastard-Konstruktor bei und ermöglichen Sie dem Verbraucher, ein Objekt "neu zu erstellen" und damit zu arbeiten und die optionale statische Abhängigkeit von DefaultThingSource zu bewältigen.

Junge, # 3 scheint sicher attraktiv. Gibt es eine andere, bessere Option? # 1 oder # 2 scheinen es einfach nicht wert zu sein.




Ich stimme für # 3. Sie werden Ihr Leben - und das Leben anderer Entwickler - einfacher machen.




Für das, was es wert ist, sieht der Standardcode, den ich in Java gesehen habe, so aus:

public class ThingMaker  {
    private IThingSource  iThingSource;

    public ThingMaker()  {
        iThingSource = createIThingSource();
    }
    public virtual IThingSource createIThingSource()  {
        return new DefaultThingSource();
    }
}

Jeder, der kein DefaultThingSource Objekt haben möchte, kann createIThingSource überschreiben. (Wenn möglich, wäre der Aufruf von createIThingSource an einer anderen Stelle als dem Konstruktor.) C # ermutigt kein Überschreiben wie Java, und es ist möglicherweise nicht so offensichtlich wie in Java, dass die Benutzer ihre eigene IThingSource bereitstellen können Implementierung. (Es ist auch nicht so offensichtlich, wie man es bereitstellt.) Ich rate, dass # 3 der richtige Weg ist, aber ich dachte, ich würde das erwähnen.




Verfügen über eine interne Factory (intern in Ihrer Bibliothek), die die DefaultThingSource IThingSource zuordnet, die von dem Standardkonstruktor aufgerufen wird.

Dadurch können Sie die ThingMaker-Klasse ohne Parameter oder Kenntnisse von IThingSource und ohne direkte Abhängigkeit von DefaultThingSource neu erstellen.




Du bist unzufrieden mit der OO-Unreinheit dieser Abhängigkeit, aber du sagst nicht wirklich, welche Schwierigkeiten sie letztendlich verursacht.

  • Verwendet ThingMaker DefaultThingSource in irgendeiner Weise, die IThingSource nicht entspricht? Nein.
  • Könnte es zu einer Zeit kommen, in der Sie den parameterlosen Konstrukteur aufgeben müssten? Da Sie zu diesem Zeitpunkt eine Standardimplementierung bereitstellen können, ist dies unwahrscheinlich.

Ich denke, das größte Problem hier ist die Wahl des Namens, nicht, ob man die Technik benutzt.




Ich unterstütze Option # 1 mit einer Erweiterung: mache DefaultThingSource einer öffentlichen Klasse . Ihre obige Formulierung impliziert, dass DefaultThingSource vor öffentlichen Konsumenten der API verborgen wird, aber da ich Ihre Situation verstehe, gibt es keinen Grund, den Standard nicht zu veröffentlichen. Außerdem können Sie leicht dokumentieren, dass außerhalb spezieller Umstände immer eine new DefaultThingSource() an den ThingMaker .