Powinieneś użyć technik, aby rozwiązać problemy, które są dobre w rozwiązywaniu problemów. Odwrócenie zależności i wtrysk nie różnią się.
Inwersja lub wstrzykiwanie zależności jest techniką, która pozwala Twojemu kodowi decydować, która implementacja metody zostanie wywołana w czasie wykonywania. Maksymalizuje to zalety późnego wiązania. Technika jest konieczna, gdy język nie obsługuje zastępowania w czasie wykonywania funkcji innych niż instancja. Na przykład Java nie ma mechanizmu zastępującego wywołania metody statycznej wywołaniami innej implementacji; w przeciwieństwie do Pythona, gdzie wszystko, co jest konieczne do zastąpienia wywołania funkcji, to powiązanie nazwy z inną funkcją (ponowne przypisanie zmiennej przechowującej funkcję).
Dlaczego chcielibyśmy różnicować implementację funkcji? Istnieją dwa główne powody:
- Chcemy wykorzystywać podróbki do celów testowych. To pozwala nam przetestować klasę, która zależy od pobierania bazy danych bez faktycznego łączenia się z bazą danych.
- Musimy wspierać wiele wdrożeń. Na przykład może być konieczne skonfigurowanie systemu obsługującego bazy danych MySQL i PostgreSQL.
Możesz także wziąć pod uwagę odwrócenie kontenerów kontrolnych. Jest to technika, która ma na celu pomóc ci uniknąć dużych, splątanych drzew konstrukcyjnych, które wyglądają jak ten pseudokod:
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)
);
Pozwala zarejestrować zajęcia, a następnie wykonuje dla Ciebie konstrukcję:
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.
Zauważ, że najłatwiej jest, jeśli zarejestrowane klasy mogą być bezpaństwowymi singletonami .
Słowo ostrzeżenia
Zauważ, że inwersja zależności nie powinna być twoją podstawową odpowiedzią na logikę oddzielania. Poszukaj możliwości użycia zamiast tego parametryzacji . Rozważ tę metodę pseudokodu, na przykład:
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;
}
Możemy użyć inwersji zależności dla niektórych części tej metody:
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;
}
Ale nie powinniśmy, przynajmniej nie do końca. Zauważ, że stworzyliśmy klasę stanowąQuerier
. Teraz zawiera odwołanie do jakiegoś zasadniczo globalnego obiektu połączenia. Powoduje to takie problemy, jak trudności w zrozumieniu ogólnego stanu programu i sposobu koordynacji między poszczególnymi klasami. Zwróć też uwagę, że jesteśmy zmuszeni sfałszować kwerendę lub połączenie, jeśli chcemy przetestować logikę uśredniania. Dalej Lepszym podejściem byłoby zwiększenie parametryzacji :
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);
}
}
Połączenie byłoby zarządzane na jeszcze wyższym poziomie, który jest odpowiedzialny za całą operację i wie, co zrobić z tym wyjściem.
Teraz możemy przetestować logikę uśredniania całkowicie niezależnie od zapytań, a ponadto możemy jej użyć w wielu różnych sytuacjach. Możemy zapytać, czy w ogóle potrzebujemy obiektów MyQuerier
i Averager
, a może odpowiedź jest taka, że nie zrobimy tego, jeśli nie zamierzamy przeprowadzać testów jednostkowych StuffDoer
, a nie testowanie jednostkowe StuffDoer
byłoby całkowicie rozsądne, ponieważ jest tak ściśle powiązane z bazą danych. Bardziej sensowne byłoby po prostu pozwolić, aby testy integracji obejmowały to. W takim przypadku możemy być w porządku fetchAboveMin
i averageData
stosować metody statyczne.