Вы должны использовать методы для решения проблем, которые они хорошо решают, когда у вас есть эти проблемы. Инверсия зависимости и впрыск ничем не отличаются.
Инверсия или внедрение зависимостей - это метод, который позволяет вашему коду решать, какая реализация метода вызывается во время выполнения. Это максимизирует преимущества позднего связывания. Техника необходима, когда язык не поддерживает замену неэкземплярных функций во время выполнения. Например, в Java отсутствует механизм для замены вызовов статического метода вызовами другой реализации; в отличие от Python, где все, что необходимо для замены вызова функции, - это привязать имя к другой функции (переназначить переменную, содержащую функцию).
Почему мы хотим варьировать реализацию функции? Есть две основные причины:
- Мы хотим использовать подделки для тестирования. Это позволяет нам тестировать класс, который зависит от выборки из базы данных, без фактического подключения к базе данных.
- Нам нужно поддерживать несколько реализаций. Например, нам может потребоваться настроить систему, которая поддерживает базы данных MySQL и PostgreSQL.
Вы также можете принять к сведению инверсию контрольных контейнеров. Это метод, предназначенный для того, чтобы помочь вам избежать огромных, запутанных конструкционных деревьев, которые выглядят так:
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)
);
Это позволяет вам зарегистрировать ваши классы, а затем делает конструкцию для вас:
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.
Обратите внимание, что проще всего, если зарегистрированные классы могут быть одиночными без состояния .
Слово предостережения
Обратите внимание, что инверсия зависимостей не должна быть вашим основным ответом для логики развязки. Ищите возможности использовать параметризацию вместо этого. Рассмотрим этот метод псевдокода, например:
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;
}
Мы могли бы использовать инверсию зависимостей для некоторых частей этого метода:
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;
}
Но мы не должны, по крайней мере, не полностью. Обратите внимание, что мы создали класс с состояниемQuerier
. Теперь он содержит ссылку на некоторый по существу глобальный объект соединения. Это создает проблемы, такие как трудности в понимании общего состояния программы и того, как разные классы координируются друг с другом. Также обратите внимание, что мы вынуждены выдумывать запрос или соединение, если хотим проверить логику усреднения. Кроме того, лучшим подходом было бы увеличение параметризации :
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);
}
}
И соединение будет управляться на каком-то еще более высоком уровне, который отвечает за работу в целом и знает, что делать с этим выводом.
Теперь мы можем проверить логику усреднения полностью независимо от запросов, и, более того, мы можем использовать ее в более широком диапазоне ситуаций. Мы могли бы подвергнуть сомнению , даже ли нам нужны MyQuerier
и Averager
объекты, а может быть , ответ в том , что мы не делаем , если мы не намерены модульное тестирование StuffDoer
, а не модульное тестирование StuffDoer
было бы вполне разумно , так как он так тесно связан с базой данных. Возможно, имеет смысл просто позволить интеграционным тестам охватить это. В этом случае, мы могли бы хорошо работать fetchAboveMin
и averageData
в статических методах.