Чому я повинен використовувати ін'єкцію залежності?


95

Мені важко шукати ресурси щодо того, чому мені слід використовувати ін'єкцію залежності . Більшість ресурсів, які я бачу, пояснюють, що він просто передає екземпляр об'єкта в інший екземпляр об'єкта, але чому? Це лише для чистішої архітектури / коду чи це впливає на продуктивність в цілому?

Чому я повинен робити наступне?

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Замість наступного?

class Profile {
    public function deactivateProfile()
    {
        $setting = new Setting();
        $setting->isActive = false;
    }
}

8
Ви вводите жорстко закодовану залежність відключитиProfile () (що погано). У вас є більше роз'єднаного коду в першому, що полегшує зміни та тестування.
Ауліс Ронкайнен

3
Чому б ти зробив перший? Ви проходите в налаштуваннях, а потім ігноруєте його значення.
Phil N DeBlanc

57
Я не погоджуюсь із судженнями. Хоча тематика може вважатись тривіальною для експертів, питання має гідність: якщо слід застосовувати інверсію залежності, то для її використання має бути обґрунтування.
Flater

13
@PhilNDeBlanc: Цей код явно надто спрощений і насправді не свідчить про логіку реального світу. Однак, deactivateProfileпропонує мені, щоб встановити значення isActivefalse, не піклуючись про його попередній стан, тут є правильним підходом. Виклик методу по суті означає, що ви маєте намір встановити його як неактивний, а не отримати його поточний (не) активний статус.
Flater

2
Ваш код не є прикладом введення залежностей або інверсії. Це приклад параметризації (що часто набагато краще, ніж DI).
jpmc26

Відповіді:


104

Перевага полягає в тому, що без введення залежності ваш клас профілю

  • потрібно знати, як створити об’єкт "Налаштування" (порушує принцип єдиної відповідальності)
  • Завжди створює об’єкт "Налаштування" однаково (створює щільне з'єднання між цими двома)

Але з ін'єкцією залежності

  • Логіка створення об’єктів Налаштування - десь інша
  • Використовувати різні типи об’єктів налаштувань просто

У цьому конкретному випадку це може здатися (або навіть бути) не має значення, але уявіть, якщо ми говоримо не про об'єкт "Налаштування", а про об'єкт DataStore, який може мати різні реалізації: той, який зберігає дані у файлах, та інший, який зберігає їх у базу даних. А для автоматизованих тестів вам також потрібна макетна реалізація. Тепер ви дійсно не хочете, щоб клас профілю твердо кодував, який він використовує - і ще важливіше, що ви дійсно не хочете, щоб клас профілю знав про шляхи файлової системи, з'єднання БД та паролі, тому створення об’єктів DataStore має статися десь ще.


23
This may seem (or even be) irrelevant in this particular caseЯ думаю, що це дуже актуально, насправді. Як би ви отримали налаштування? Багато систем, які я бачив, матимуть жорсткий за замовчуванням набір налаштувань та конфігурацію для загального користування, тому вам потрібно буде завантажити обоє та перезаписати деякі значення із загальнодоступними налаштуваннями. Можливо, вам навіть знадобиться кілька джерел за замовчуванням. Можливо, ви навіть можете отримати деякі з диска, інші з БД. Отже, вся логіка рівномірного отримання налаштувань може, і часто є нетривіальною, - безумовно, не є чимось споживчим кодом, який повинен або не турбує.
ВЛАЗ

Можна також відзначити, що ініціалізація об'єктів для нетривіального компонента, такого як веб-сервіс, зробить $setting = new Setting();жахливо неефективним. Інжекція та інстанція об'єктів відбувається один раз.
vikingsteve

9
Я думаю, що використання макетів для тестування повинно робити більше уваги. Швидше за все, якщо ви подивитесь лише на код, він завжди буде об’єктом "Налаштування" і ніколи не зміниться, тому передача його здається немов марним зусиллям. Однак у перший раз, коли ви намагаєтеся протестувати об’єкт профілю самостійно, не потребуючи також об'єкта "Налаштування" (використовуючи замість цього об'єкт макету як рішення), потреба дуже очевидна.
JPhi1618

2
@ JPhi1618 Я думаю, що проблема з підкресленням "DI призначена для тестування одиниць" полягає в тому, що це просто призводить до питання "навіщо мені потрібні одиничні тести". Відповідь може здатися вам очевидною, і переваги, безумовно, є, але хтось тільки починає, кажучи: "Вам потрібно зробити цю складну звучання, щоб зробити цю іншу складну звучання", як правило, трохи відключення. Тому добре згадати різні переваги, які можуть бути більш застосовні до того, що вони роблять зараз.
IMSoP

1
@ user986730 - це дуже хороший момент, і підкреслюється, що справжнім принципом тут є залежність від інверсії , інжекція - це лише одна техніка. Наприклад, в C, ви не можете реально вводити залежності за допомогою бібліотеки IOC, але ви можете інвертувати їх під час компіляції, включаючи різні вихідні файли для ваших макетів тощо, що має той самий ефект.
Стівен Бірн

64

Введення залежностей полегшує тестування вашого коду.

Я дізнався це з перших вуст, коли мені поставили завдання виправити важко виявлену помилку в інтеграції PayPal Magento.

Проблема виникне, коли PayPal повідомив Magento про невдалий платіж: Magento не зареєстрував несправність належним чином.

Тестування потенційного виправлення "вручну" було б дуже стомлюючим: вам потрібно якось запустити сповіщення PayPal "Не вдалося". Вам доведеться подати електронний чек, скасувати його та дочекатися його помилки. Це означає 3+ днів для перевірки зміни коду на один символ!

На щастя, здається, що основні розробники Magento, які розробили цю функцію, мали на увазі тестування, і використовували схему введення залежності, щоб зробити її тривіальною. Це дозволяє нам перевірити свою роботу за допомогою простого тестового випадку, як цей:

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test PayPal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

Я впевнений, що у моделі ДІ є безліч інших переваг, але найбільша користь у моїй свідомості - це підвищена доказливість .

Якщо вам цікаво рішення цієї проблеми, перегляньте репортаж GitHub тут: https://github.com/bubbleupdev/BUCorefix_Paypalstatus


4
Введення залежності залежно від тестування коду, ніж код із твердо кодованими залежностями. Усунути залежності від ділової логіки взагалі ще краще.
Мураха P

1
І один із головних способів зробити те, що пропонує @AntP, - це параметризація . Логіка перетворення результатів, що повертаються з бази даних, в об'єкт, що використовується для заповнення шаблону сторінки (загальновідоме як "модель"), навіть не повинен знати, що відбувається видобуток; просто потрібно отримати ці об'єкти як вхідні дані.
jpmc26

4
@ jpmc26 дійсно - я схильний працювати над функціональним ядром, імперативною оболонкою , яка насправді є просто фантазійною назвою для передачі даних у ваш домен, а не введення в нього залежностей. Домен чистий, його можна перевірити без макетів, а потім просто загорнутий у оболонку, яка адаптує такі речі, як наполегливість, обмін повідомленнями тощо.
Ant P

1
Я думаю, що єдиний фокус на доказовості є шкідливим для прийняття DI. Це робить малопривабливим для людей, які відчувають, що їм або не потрібно багато тестування, або вважають, що тестування вже є під контролем. Я зауважу, що писати чистий, багаторазовий код практично неможливо без DI. Тестування дуже далеко в списку переваг, і невтішно бачити цю відповідь першим чи другим у кожному запитанні про переваги DI.
Карл Лет

26

Чому (у чому навіть питання)?

Чому я повинен використовувати ін'єкцію залежності?

Найкраще мнемонічне для мене це " нове - це клей ": Кожен раз, коли ви використовуєте newу своєму коді, цей код прив'язується до конкретної реалізації . Якщо ви неодноразово використовуєте нове в конструкторах, ви створите ланцюжок конкретних реалізацій . І оскільки ви не можете "мати" екземпляр класу, не будуючи його, ви не можете розділити цей ланцюг.

Як приклад, уявіть, що ви пишете відеогра-гоночний автомобіль. Ви почали з класу Game, який створює a RaceTrack, який створює 8 Cars, які кожен створює Motor. Тепер, якщо ви хочете 4 додаткові Carsз різним прискоренням, вам доведеться змінити кожен згаданий клас , за винятком, можливо Game.

Чистіший код

Це лише для чистішої архітектури / коду

Так .

Однак це може здатися менш зрозумілим у цій ситуації, оскільки це більше приклад того, як це зробити . Фактична перевага лише показує, коли задіяно кілька класів, і це важче продемонструвати, але уявіть, що ви використали б DI у попередньому прикладі. Код, що створює всі ці речі, може виглядати приблизно так:

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

Додавання цих 4 різних автомобілів тепер можна зробити, додавши наступні рядки:

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • Ніяких змін RaceTrack, Game, Car, або Motorбули необхідні - це означає , що ми можемо бути 100% впевнені , що ми не вводити ніяких нових помилок там!
  • Замість того, щоб переходити між кількома файлами, ви зможете одночасно побачити повну зміну на екрані. Це результат сприйняття створення / налаштування / конфігурації як власної відповідальності - це не робота автомобіля зі створення двигуна.

Міркування щодо продуктивності

чи це впливає на ефективність в цілому?

Ні . Але якщо бути повністю чесним з тобою, можливо.

Однак навіть у такому випадку це така смішно мала кількість, що вам не потрібно дбати. Якщо в якийсь - то момент в майбутньому, ви повинні написати код для тамагочі з еквівалентом 5MHZ процесора і оперативної пам'яті 2 Мб, то , можливо , ви , можливо , доведеться піклуватися про це.

У 99,999% * випадків він матиме кращу ефективність, оскільки ви витрачали менше часу на виправлення помилок і більше часу на вдосконалення алгоритмів, важких для ресурсів.

* повністю складений номер

Додана інформація: "жорстко"

Не помиляйтесь, це все ще дуже «жорстко» - цифри записуються безпосередньо в коді. Не жорстке кодування означало б щось на зразок зберігання цих значень у текстовому файлі - наприклад, у форматі JSON - та читанні їх із цього файлу.

Для цього вам потрібно додати код для читання файлу та розбору JSON. Якщо ви ще раз розглянете приклад; у не-DI версії файл a Carчи Motorтепер має прочитати файл. Це не здається, що це має занадто великий сенс.

У версії DI ви додали б його до коду, що налаштовує гру.


3
Реклама жорстко закодована, різниця між кодом та конфігураційним файлом насправді не така вже й велика. Файл у комплекті із програмою є джерелом, навіть якщо ви читаєте динамічно. Перетягування значень з коду до файлів даних у json або будь-якому іншому форматі «config» нічого не допомагає, якщо ці значення не повинні перекрити користувач або залежати від середовища.
Ян Худек

3
Я фактично робив Тамагочі один раз на Arduino (16 МГц, 2 КБ)
Jungkook

@JanHudec Правда. Я фактично мав довше пояснення там, але вирішив видалити його, щоб він був коротшим і зосередився на тому, як це стосується DI. Є більше речей, які не на 100% вірні; загалом відповідь оптимізованіший, щоб підштовхнути "точку" DI, не надто довго. Або по-іншому, це те, що я хотів би почути, коли я починав з DI.
Р. Шмітц

17

Мене завжди вражали ін'єкції залежності. Здавалося, він існує лише у сферах Java, але ці сфери говорили про це з великою повагою. Ви бачите, це був один із великих шаблонів, які, як кажуть, наводять порядок до хаосу. Але приклади завжди були суперечливими та штучними, встановлюючи непроблемну проблему, а потім вирішуючи її вирішити, ускладнивши код.

Це мало більше сенсу, коли співрозмовник Python передав мені цю мудрість: це просто передача аргументів функціям. Це ледь не візерунок; більше нагадує, що ви можете попросити щось як аргумент, навіть якщо ви могли б самі собі забезпечити розумну цінність.

Тож ваше запитання приблизно еквівалентне "чому моя функція повинна брати аргументи?" і має багато однакових відповідей, а саме: дозволити абоненту приймати рішення .

Звичайно, це пов'язано з вартістю, оскільки тепер ви змушуєте абонента прийняти якесь рішення (якщо ви не зробите аргумент необов'язковим), а інтерфейс дещо складніший. В обмін ви отримуєте гнучкість.

Отже: Чи є вагома причина, що саме вам потрібно використовувати саме цей Settingтип / значення? Чи є вагома причина, що для виклику коду може бути потрібний інший Settingтип / значення? (Пам'ятайте, тести - код !)


2
Так. Нарешті, IoC натиснув на мене, коли зрозумів, що йдеться просто про "дозволити абоненту приймати рішення". Це IoC означає, що управління компонентом зміщується з автора компонента на користувача компонента. А оскільки на той момент у мене вже було достатньо зчеплення з програмним забезпеченням, яке вважало себе розумнішим за мене, мене вмить продали на підході DI.
Joker_vD

1
"Це просто передача аргументів функціям. Це ледве не закономірність" Це єдине хороше або навіть зрозуміле пояснення DI, яке я чув (ну більшість людей навіть не говорять про те, що це таке , а скоріше про його переваги). І тоді вони використовують складний жаргон, як "інверсія управління" та "нещільне з'єднання", які просто потребують більше питань, щоб зрозуміти, коли це дійсно проста ідея.
Аддісон

7

Приклад, який ви наводите, - це не введення залежності в класичному розумінні. Ін'єкція залежності зазвичай стосується передачі об'єктів у конструкторі або з використанням "ін'єкції сеттера" безпосередньо після створення об'єкта, щоб встановити значення для поля в новоствореному об'єкті.

Ваш приклад передає об'єкт як аргумент методу екземпляра. Цей метод екземпляра потім змінює поле на цьому об'єкті. Ін'єкційна залежність? Ні. Порушення інкапсуляції та приховування даних? Абсолютно!

Тепер, якщо код був таким:

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

Тоді я б сказав, що ви використовуєте ін'єкцію залежності. Помітна різниця - це Settingsоб'єкт, що передається конструктору або Profileоб'єкту.

Це корисно, якщо об’єкт «Налаштування» дорогий або складний для побудови, або Параметри - це інтерфейс або абстрактний клас, де існує кілька конкретних реалізацій для зміни поведінки часу виконання.

Оскільки ви безпосередньо отримуєте доступ до поля об’єкта "Налаштування", а не викликаєте метод, ви не можете скористатися поліморфізмом, що є однією з переваг введення залежності.

Схоже, що Налаштування профілю є специфічними для цього профілю. У цьому випадку я зробив би одне з наступних:

  1. Ігноруйте об'єкт "Налаштування" всередині конструктора профілю

  2. Передайте об’єкт «Налаштування» в конструктор і скопіюйте окремі поля, які застосовуються до профілю

Чесно кажучи, передача об'єкта "Налаштування" в, deactivateProfileа потім зміна внутрішнього поля об'єкта "Налаштування" - це запах коду. Об'єкт "Налаштування" повинен бути єдиним, що змінює свої внутрішні поля.


5
The example you give is not dependency injection in the classical sense.- Не має значення. У людей з об'єктами є мозок, але ти все-таки передаєш залежність від чогось.
Роберт Харві

1
Коли ви говорите про " в класичному розумінні ", ви, як говорить @RobertHarvey, говорять суто в термінах ОО. Наприклад, у функціональному програмуванні введення однієї функції в іншу (функції вищого порядку) - це класичний приклад парадигми введення залежності.
Девід Арно

3
@RobertHarvey: Я думаю, я брав занадто багато свобод із "введенням залежності". Місце, де люди найчастіше думають про термін і вживає цей термін, посилається на поля на об'єкт, який "вводиться" під час побудови об'єкта, або одразу після цього, у випадку введення сетера.
Грег Бургхардт

@DavidArno: Так, ви маєте рацію. ОП, здається, у запитанні має об'єктно-орієнтований фрагмент коду PHP, тому я відповідав лише на це питання, а не займався функціональним програмуванням --- все, хоча з PHP ви можете задати те саме питання з точки зору функціонального програмування.
Грег Бургхардт

7

Я знаю, що спізнююсь на цю вечірку, але відчуваю, що важливий момент пропускається.

Чому я повинен це робити:

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

Ви не повинні. Але не тому, що введення залежності - це погана ідея. Це тому, що це робить це неправильно.

Давайте розглянемо ці речі за допомогою коду. Ми будемо робити це:

$profile = new Profile();
$profile->deactivateProfile($setting);

коли ми з цього виходимо приблизно те саме:

$setting->isActive = false; // Deactivate profile

Тож, звичайно, це здається марною тратою часу. Це коли ти робиш це так. Це не найкраще використання ін'єкційної залежності. Це навіть не найкраще використання класу.

Що робити, якщо замість цього у нас було таке:

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

А тепер applicationвільна активувати та деактивувати, profileне знаючи нічого, зокрема про те, settingщо насправді змінюється. Чому це добре? У випадку, якщо вам потрібно змінити налаштування. applicationВідгороджена від цих змін , так що ви можете йти гайки в безпечному міститься просторі без необхідності дивитися все зламати , як тільки ви торкаєтеся що - то.

Це випливає із принципу окремої побудови від поведінки . Зразок DI тут простий. Побудуйте все необхідне на найнижчому рівні, з'єднайте їх, а потім почніть всю поведінку, що тикає одним дзвінком.

У результаті ви маєте окреме місце, щоб вирішити, що підключається до того, що і інше місце, щоб керувати тим, що говорить що і що.

Спробуйте це на чомусь, що вам доведеться підтримувати з часом, і подивіться, чи це не допомагає.


6

Як замовник, коли ви наймаєте механізатора, щоб зробити щось з вашим автомобілем, чи очікуєте ви, що механік побудує машину з нуля, щоб потім працювати з нею? Ні, ви даєте механіку машину, на якій хочете, щоб вони працювали .

Як власник гаража, коли ви доручаєте механіку щось робити з автомобілем, чи очікуєте ви, щоб механік створив власну викрутку / гайковий ключ / деталь автомобіля? Ні, ви надаєте механіку ті деталі / інструменти, які йому потрібні

Чому ми це робимо? Ну, подумайте. Ви власник гаража, який хоче найняти когось, щоб стати вашим механіком. Ви навчите їх бути механіком (= ви напишете код).

Що буде простіше:

  • Навчіть механіку, як прикріпити спойлер до машини за допомогою викрутки.
  • Навчіть механіку створювати автомобіль, створити спойлер, створити викрутку і потім приєднати новостворений спойлер до новоствореної машини за допомогою новоствореної викрутки.

Є велика користь від того, щоб ваш механік не створював все з нуля:

  • Очевидно, що навчання (= розробка) значно скорочується, якщо ви просто постачаєте свого механіка наявними інструментами та деталями.
  • Якщо одному і тому ж механіку доводиться виконувати одну і ту ж роботу кілька разів, ви можете переконатися, що він повторно використовує викрутку, а не завжди викидає стару та створює нову.
  • Крім того, механік, який навчився створювати все, повинен бути набагато більше експертом, і, таким чином, очікувати більш високу заробітну плату. Аналогія кодування тут полягає в тому, що клас з багатьма обов'язками підтримувати набагато складніше, ніж клас з єдиною суворо визначеною відповідальністю.
  • Крім того, коли нові винаходили на ринок, і зараз спойлери виготовляють із вуглецю замість пластику; вам доведеться перекваліфікувати (= переробити) свого експерта-механіка. Але вашому "простому" механіку не доведеться перенавчатися до тих пір, поки спойлер все-таки можна буде прикріпити таким же чином.
  • Маючи механіка, який не покладається на автомобіль, який вони створили самі, означає, що у вас є механік, який може працювати з будь-яким автомобілем, який вони можуть отримати. У тому числі машини, які ще не існували під час навчання механіка. Однак ваш досвідчений механік не зможе побудувати новіші машини, які були створені після їх навчання.

Якщо ви наймете та навчите майстра-механіка, ви збираєтеся знайти співробітника, який коштує більше, потребує більше часу, щоб виконати те, що повинно бути простою роботою, і постійно потрібно буде перекваліфікуватися, коли хтось із них багатьох обов'язків. бути оновленим.

Аналогія розробки полягає в тому, що якщо ви використовуєте класи з жорстко кодованими залежностями, вам доведеться закінчити важко підтримувати класи, які потребуватимуть постійного перероблення / зміни кожного разу, коли буде створена нова версія об'єкта ( Settingsу вашому випадку), і ви Треба буде розробити внутрішню логіку, щоб клас мав можливість створювати різні типи Settingsоб'єктів.

Крім того, той, хто споживає ваш клас, тепер також повинен буде попросити клас створити правильний Settingsоб'єкт, на відміну від простого проходження класу будь-якогоSettings об'єкт, який він хоче передати. Це означає додатковий розвиток для споживача, щоб зрозуміти, як попросити клас створити правильний інструмент.


Так, інверсія залежності вимагає трохи більше зусиль для написання замість жорсткого кодування залежності. Так, прикро більше набирати текст.

Але це той самий аргумент, як вибір жорсткого коду буквальних значень, оскільки "оголошення змінних вимагає більше зусиль". Технічно правильно, але професіонали переважують мінуси на кілька порядків .

Користь від інверсії залежності не відчувається під час створення першої версії програми. Користь від інверсії залежності відчувається, коли вам потрібно змінити або розширити цю початкову версію. І не обманюйте себе на думці, що ви все отримаєте правильно з першого разу і не потрібно буде продовжувати / змінювати код. Вам доведеться змінити речі.


це впливає на ефективність в цілому?

Це не впливає на продуктивність програми. Але це масово впливає на час розробки (і, отже, на продуктивність) розробника .


2
Якщо ви видалили свій коментар, " Як незначний коментар, ваше питання зосереджується на інверсії залежності, а не на ін'єкції залежності. Ін'єкція - це один із способів зробити інверсію, але це не єдиний спосіб ". Це буде чудовою відповіддю. Інверсія залежності може бути досягнута за допомогою ін'єкції або локатора / глобалі. Приклади запитання стосуються ін’єкцій. Так що питання є про залежність ін'єкцій (а також інверсії залежностей).
Девід Арно

12
Я думаю, що вся річ з автомобілем трохи зачуджена і заплутана
Ewan

4
@Flater, частина проблеми полягає в тому, що ніхто, схоже, не погоджується з тим, яка різниця між введенням залежності, інверсією залежності та інверсією контролю. Одне напевно, однак, «контейнер», безумовно, не потрібен для введення залежності в метод чи конструктор. Чистий (або поганий чоловік) DI спеціально описує ручне введення залежності. Це єдине введення залежності, яке я особисто використовую, оскільки не люблю "магію", пов'язану з контейнерами.
Девід Арно

1
@Flater Проблема з прикладом полягає в тому, що ви майже точно не моделюєте жодної проблеми в коді таким чином. Таким чином, це неприродно, вимушено і додає більше запитань, ніж відповідей. Це робить більше шансів ввести в оману і заплутати, ніж демонструвати.
jpmc26

2
@gbjbaanb: Ні в якому разі моя відповідь навіть віддалено не заглиблюється в поліморфізм. Немає спадкування, впровадження інтерфейсу чи нічого подібного до типів "up / downcasting". Ви повністю неправильно читаєте відповідь.
Flater

0

Як і всі візерунки, дуже справедливо запитати "чому", щоб уникнути роздутого дизайну.

Що стосується ін'єкцій залежностей, це легко зрозуміти, думаючи про два, мабуть, найважливіші аспекти дизайну ООП ...

Низька муфта

З'єднання в комп'ютерному програмуванні :

В інженерії програмного забезпечення з'єднання - це ступінь взаємозалежності між програмними модулями; міра того, наскільки тісно пов'язані дві підпрограми або модулі; міцність зв’язків між модулями.

Ви хочете досягти низької зв'язку. Дві речі, які сильно поєднуються, означають, що якщо ви зміните одне, вам, швидше за все, доведеться змінити інше. Помилки або обмеження в одному, ймовірно, спричинить помилки / обмеження в іншому; і так далі.

Об'єкти інстанціювання одного класу інших - дуже сильна зв'язок, тому що одному потрібно знати про існування другого; він повинен знати, як його інстанціювати (які аргументи конструктору потрібні), і ці аргументи повинні бути доступні при виклику конструктора. Також, залежно від того, чи потрібна мова явної деконструкції (C ++), це спричинить подальші ускладнення. Якщо ви вводите нові класи (тобто, а NextSettingsчи що завгодно), вам доведеться повернутися до початкового класу і додати більше викликів до їх конструкторів.

Висока згуртованість

Згуртованість :

У комп'ютерному програмуванні згуртованість відноситься до ступеня, в якій елементи всередині модуля належать разом.

Це друга сторона монети. Якщо ви подивитесь на одну одиницю коду (один метод, один клас, один пакет тощо), ви хочете, щоб весь код всередині цього блоку мав якомога менше обов'язків.

Основним прикладом цього може слугувати шаблон MVC: ви чітко відокремлюєте доменну модель від подання (GUI) та контрольного шару, який склеює їх.

Це дозволяє уникнути розриву коду, де ви отримуєте великі куски, які роблять багато різних речей; якщо ви хочете змінити якусь частину, вам слід також відслідковувати всі інші функції, намагаючись уникати помилок тощо; і ти швидко програмуєш себе в норі, куди дуже важко вибратися.

За допомогою введення залежності ви делегуєте створення або відстеження залежностей будь-яким класам (або файлам конфігурації), які реалізують ваш DI. Інші класи будуть не надто піклуватися про те, що саме відбувається - вони будуть працювати з деякими загальними інтерфейсами, і не мають поняття, що таке реальна реалізація, а значить, вони не відповідають за інші матеріали.


-1

Ви повинні використовувати методи для вирішення проблем, які вони добре вирішують, коли у вас є ці проблеми. Інверсія залежності та ін'єкція не відрізняються.

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


2
" Введення залежності - це техніка, яка покликана допомогти вам уникнути величезних, заплутаних будівельних дерев ... ". Ваш перший приклад після цього твердження - приклад чистої або поганої ін'єкції наркоманів. Другий - приклад використання контейнера IoC для "автоматизації" введення цих залежностей. Обидва є прикладами введення залежності в дію.
Девід Арно

@DavidArno Так, ти маєш рацію. Я скоригував термінологію.
jpmc26

Є третя основна причина змінити реалізацію або, принаймні, розробити код, припускаючи, що реалізація може змінюватись: це стимулює розробника мати вільне з'єднання і уникати написання коду, який буде важко змінити, якщо нова реалізація буде впроваджена через деякий час майбутнє. І хоча це може не бути пріоритетним для деяких проектів (наприклад, знаючи, що додаток ніколи не буде переглянуто після його первинного випуску), це буде в інших (наприклад, де бізнес-модель компанії спеціально намагається запропонувати розширену підтримку / розширення своїх додатків. ).
Flater

@ Інжекція залежної від кольору все ще призводить до сильної зв'язку. Він пов'язує логіку з певним інтерфейсом і вимагає від відповідного коду знати про те, що робить цей інтерфейс. Розглянемо код, який перетворює результати, отримані з БД. Якщо я використовую DI, щоб розділити їх, тоді код все-таки повинен знати, що відбувається вилучення БД і викликати його. Краще рішення, якщо код перетворення навіть не знає, що відбувається . Єдиний спосіб зробити це - це, якщо результати вибору передає абонент, а не вводить його.
jpmc26

1
@ jpmc26 Але у вашому (коментарі) прикладі база даних навіть не повинна була бути залежною для початку. Звичайно, уникати залежностей краще для вільного зв'язку, але DI зосереджується на впровадженні залежностей, які необхідні.
Flater
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.