Ви повинні використовувати методи для вирішення проблем, які вони добре вирішують, коли у вас є ці проблеми. Інверсія залежності та ін'єкція не відрізняються.
Інверсія або ін'єкція залежності - це техніка, яка дозволяє вашому коду вирішувати, яка реалізація методу викликається під час виконання. Це максимізує переваги пізнього зв’язування. Ця методика необхідна, коли мова не підтримує заміну виконуваних функцій на інстанції часу. Наприклад, у 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
в статичні методи.