Sie sollten Techniken verwenden, um die Probleme zu lösen, die sie gut lösen können, wenn Sie diese Probleme haben. Abhängigkeitsinversion und Injektion sind nicht unterschiedlich.
Abhängigkeitsinversion oder -injektion ist eine Technik, mit der Ihr Code entscheiden kann, welche Implementierung einer Methode zur Laufzeit aufgerufen wird. Dies maximiert die Vorteile einer späten Bindung. Die Technik ist erforderlich, wenn die Sprache das Ersetzen von Nicht-Instanz-Funktionen zur Laufzeit nicht unterstützt. Beispielsweise fehlt in Java ein Mechanismus, um Aufrufe einer statischen Methode durch Aufrufe einer anderen Implementierung zu ersetzen. Im Gegensatz zu Python müssen Sie zum Ersetzen des Funktionsaufrufs nur den Namen an eine andere Funktion binden (die Variable, die die Funktion enthält, neu zuweisen).
Warum sollten wir die Implementierung der Funktion variieren wollen? Es gibt zwei Hauptgründe:
- Wir wollen Fälschungen zu Testzwecken verwenden. Auf diese Weise können wir eine Klasse testen, die von einem Datenbankabruf abhängt, ohne tatsächlich eine Verbindung zur Datenbank herzustellen.
- Wir müssen mehrere Implementierungen unterstützen. Beispielsweise müssen wir möglicherweise ein System einrichten, das sowohl MySQL- als auch PostgreSQL-Datenbanken unterstützt.
Möglicherweise möchten Sie auch die Inversion von Steuercontainern beachten. Dies ist eine Technik, die Ihnen helfen soll, riesige, verworrene Konstruktionsbäume zu vermeiden, die wie folgt aussehen:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
Sie können Ihre Klassen registrieren und dann die Konstruktion für Sie durchführen:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Beachten Sie, dass es am einfachsten ist, wenn die registrierten Klassen zustandslose Singletons sein können .
Achtung!
Beachten Sie, dass die Abhängigkeitsinversion nicht Ihre erste Antwort für die Entkopplungslogik sein sollte. Suchen Sie nach Möglichkeiten, stattdessen die Parametrisierung zu verwenden. Betrachten Sie diese Pseudocode-Methode zum Beispiel:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
Wir könnten die Abhängigkeitsinversion für einige Teile dieser Methode verwenden:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
Das sollten wir aber zumindest nicht ganz. Beachten Sie, dass wir mit eine stateful- Klasse erstellt haben Querier
. Es enthält jetzt einen Verweis auf ein im Wesentlichen globales Verbindungsobjekt. Dies führt zu Problemen wie dem schwierigen Verständnis des Gesamtzustands des Programms und der Koordinierung der verschiedenen Klassen. Beachten Sie auch, dass wir gezwungen sind, den Abfrager oder die Verbindung auszutricksen, wenn wir die Mittelungslogik testen möchten. Ein besserer Ansatz wäre, die Parametrisierung zu erhöhen :
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
Und die Verbindung würde auf einer noch höheren Ebene verwaltet, die für den gesamten Vorgang verantwortlich ist und weiß, was mit dieser Ausgabe zu tun ist.
Jetzt können wir die Mittelungslogik völlig unabhängig von der Abfrage testen und sie darüber hinaus in einer Vielzahl von Situationen einsetzen. Wir könnten uns fragen, ob wir überhaupt die MyQuerier
und Averager
-Objekte benötigen , und vielleicht lautet die Antwort, dass dies nicht der Fall ist, wenn wir nicht beabsichtigen, einen Komponententest durchzuführen StuffDoer
, und ein Komponententest StuffDoer
wäre absolut vernünftig, da er so eng mit der Datenbank verbunden ist. Es könnte sinnvoller sein, sich nur von Integrationstests abdecken zu lassen. In diesem Fall könnten wir fein machen sein fetchAboveMin
und averageData
in statischen Methoden.