10 Stimmen

Dependency Injection Optional Parameters

Ist es als schlechte Praxis zu betrachten, optionale Parameter zu verwenden, wenn Dependency-Injection-Frameworks mit Constructor-Injection verwendet werden?

Beispiel:

public class ProductsController
{
    public ProductsController(IProductService productService = null, IBackOrderService = null)
    {
    }
}

Ich habe beide Parameter als optional angegeben, aber mein DI-Framework wird immer beide Abhängigkeiten einfügen. Wenn ich eine neue Aktion zu meinem Controller hinzufüge, die eine neue Abhängigkeit erfordert, wäre es schlecht, diese neue Abhängigkeit optional zu machen? Ich könnte potenziell Dutzende von Unittests beeinträchtigen, obwohl die bestehenden Tests die neue Abhängigkeit nicht benötigen würden.

Bearbeiten
Die Leute scheinen von meiner Frage verwirrt zu sein. Ich erstelle nie manuell einen ProductsController in meiner Webanwendung. Dies wird von einer Controller-Fabrik behandelt (die Abhängigkeiten automatisch einfügt).

Was mir nicht gefällt, ist ein Unittest wie dieser:

[Test]
public void Test1()
{
    var controller = new ProductsController(new MockProductService(), new MockBackOrderService());
}

Jetzt entscheide ich mich, eine neue Aktionsmethode zu meinem Controller hinzuzufügen. Diese neue Aktion benötigt eine neue Abhängigkeit, aber keine der vorhandenen Aktionen. Jetzt muss ich zurückgehen und 100 verschiedene Unittests ändern, weil ich einen neuen Parameter hinzugefügt habe. Ich kann das vermeiden, indem ich die Parameter optional mache, aber ich wollte wissen, ob das eine schlechte Idee war. Mein Bauchgefühl sagt nein, weil es nur die Unittests betrifft.

15voto

Piotr Kula Punkte 9253

Ich stimme voll und ganz mit der akzeptierten Antwort überein für alle Fälle, bei denen die Definition einer Abhängigkeit bedeutet, dass die Implementierung ohne sie nicht funktioniert.

Aber was ist, wenn Sie etwas haben, das nicht unbedingt auf eine Abhängigkeit angewiesen ist, Sie jedoch etwas konfigurieren möchten, wenn diese Abhängigkeit geladen wurde. OK...? das klingt etwas seltsam, aber es ist ein gültiger Anwendungsfall für Metaprogrammierung - und Sie denken vielleicht, dass das Factory-Muster helfen könnte.. aber selbst die Factory könnte einige, keine oder alle Abhängigkeiten benötigen, daher löst die Factory dieses Problem nicht.

Das reale Problem mit Feature-Flags. Wenn ein Feature deaktiviert ist, benötigen Sie diese Abhängigkeit nicht.. oder können diese vielleicht nicht einmal erstellen, weil es keine konkrete Implementierung gibt. Also ist es deaktiviert. Es kompiliert, alles funktioniert gut. Aber dann schaltet jemand das Feature ein und plötzlich benötigen wir diese Abhängigkeit.

Ich habe einen Weg gefunden, dies zu tun - und das Beste daran ist, ich habe erst kürzlich herausgefunden, wie man dies durch das Erlernen einer nicht so bekannten Technik mit Dependency Injection (Ich verwende Microsoft.Extensions.DependencyInjection) erreichen kann

Einfügen mehrerer konkreter Implementierungen für ein einziges Interface

services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();

Ich habe kürzlich gelernt, dass dies vollkommen gültig ist, aber dafür muss Ihr Konstruktor so aussehen...

public WarehouseHandler(IEnumerable repos)

Das ist wirklich cool! Ich kann ein Repository auswählen, das ich basierend auf beliebigen Kriterien benötige. Aber wie hilft das bei optionalen Abhängigkeiten?

Weil dieser Typ von Konstruktor Ihnen 0 oder mehr Abhängigkeiten geben wird. Also, wenn Sie keine Abhängigkeiten hinzufügen, können Sie im Konstruktor mit einer bedingten Anweisung verzweigen

  if (repos.Count() == 0)
    return;

Dies ist null-referenzsicher, erfordert keine Standardwerte, einfach zu debuggen, einfach zu lesen und einfach umzusetzen.

Sie können diese Technik jetzt auch als Mechanismus für den Feature-Schalter verwenden!

7voto

Tomasz Jaskuλa Punkte 15373

Ich glaube nicht, dass es eine gute Idee ist. Konstruktorinjektion bedeutet, dass die Abhängigkeiten erforderlich sind. Sogar sollten Sie die Guard-Zeilen hinzufügen, die eine Ausnahme auslösen, wenn einer der Parameter null ist.

Ich glaube, das Problem liegt bei Ihren Modultests. Zum Beispiel habe ich nur eine Stelle, an der der Controller erstellt und unterstützende Objekte (ControllerContext, HttpContext, request, Response usw.) gemockt werden. Wenn ich dann einen neuen Parameter im Konstruktor hinzufüge, muss ich ihn nur an einer Stelle in den Modultests ändern.

Vielleicht sollten Sie in Betracht ziehen, eine generische Basisklasse in Ihren Modultests zu erstellen oder die "Setup"-Routine für die Tests zu verwenden.

0voto

Michael Freidgeim Punkte 23629

Erwägen Sie, das Muster des Testdatenbuilders zu verwenden.

In seiner einfachsten Form könnte es als statische Testhilfsmethode durchgeführt werden

public ProductsController BuildControllerWIthMockDependencies ()
{
    var controller = new ProductsController(new MockProductService(), new MockBackOrderService());
return controller;
}

Sie können AutoFixture als generischen Testdaten-Builder verwenden.

Weitere Techniken für die Verwendung von Testdaten-Buildern finden Sie in Testdaten-Builder: eine Alternative zum Object Mother-Muster

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X