Які переваги введення залежності залежно від випадків, коли майже кожному потрібен доступ до загальної структури даних?


20

Є багато причин, через які глобальні люди є злими в ООП.

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

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

Приклад (спрощений, щоб показати точку взагалі, не заглиблюючись занадто глибоко в конкретну програму)

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

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

vector<Vehicle> vehicles;

до якого можна отримати доступ з будь-якого місця.

Більш сприятливим для OOP рішенням буде контейнер бути членом класу, який обробляє цикл основної події, і бути екземпляром в його конструкторі. Кожен клас, який йому потрібен і є членом основної нитки, отримає доступ до контейнера за допомогою вказівника в їх конструкторі. Наприклад, якщо зовнішнє повідомлення надходить через мережеве з'єднання, клас (по одному для кожного з'єднання), який обробляє розбір, приймає на себе, і аналізатор матиме доступ до контейнера за допомогою вказівника чи посилання. Тепер, якщо проаналізоване повідомлення призводить до зміни елементу контейнера або потрібні деякі дані з нього, щоб виконати дію, з ним можна обробляти без необхідності перекидання тисяч змінних за допомогою сигналів і слотів (або гірше, зберігання їх у аналізаторі, щоб пізніше був отриманий тим, хто викликав парсер). Звичайно, всі класи, які отримують доступ до контейнера через введення залежності, є частиною однієї теми. Різні потоки не матимуть прямого доступу до нього, але виконують свою роботу і потім надсилають сигнали в основний потік, а слоти в основному потоці оновлять контейнер.

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

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


6
Зачекайте, ви говорите про не мінливі глобали та використовуєте посилання на те, "чому глобальна держава погана", щоб виправдати це? WTF?
Теластин

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

9
Але існує рішуча різниця між лише глобальними читаннями та глобальним станом.
Теластин


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

Відповіді:


37

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

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

Суть введення залежності не просто в тому, щоб кожен актор, якому потрібен певний ресурс, міг його мати, адже очевидно, якщо ви зробите всі ресурси глобальними, то кожен актор матиме доступ до кожного ресурсу, вирішеної проблеми, правда?

Точка введення залежності:

  1. Дозволити акторам отримувати доступ до ресурсів на необхідних засадах та
  2. Мати контроль над тим, який екземпляр ресурсу має доступ до будь-якого актора.

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

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

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

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

Тоді ви повинні задуматися: чи дійсно всім акторам потрібен доступ до цього ресурсу? справді?

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

Що стосується глобальних даних, кожен рядок вихідного коду у всій вашій програмі має доступ до кожного глобального ресурсу. За допомогою введення залежності кожен екземпляр ресурсу видно лише тим суб'єктам, які насправді потребують цього. Якщо обидва є однаковими (суб'єкти, які потребують певного ресурсу, складають 100% рядків вихідного коду у вашому проекті), ви, мабуть, помилилися у своєму дизайні. Отже, або

  • Перетворюючи цей великий величезний ресурс бога на менші субресурси, тому різним акторам потрібен доступ до різних його фрагментів, але рідко актору потрібні всі його частини, або

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


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

3
@gbjbaanb Припустимо, що ресурсом є Zork. ОП каже, що не тільки кожному об'єкту в його системі потрібен Зорк для роботи, але також, що тільки один Зорк коли-небудь існуватиме, і що всі його об'єкти потребуватимуть лише доступу до цього одного екземпляра Зорка. Я кажу, що жодне з цих двох припущень не є розумним: це, мабуть, неправда, що всі його об'єкти потребують Зорка, і будуть випадки, коли деяким з об’єктів знадобиться певний екземпляр Зорка, тоді як інші об’єкти потребують різні екземпляри Зорка.
Майк Накіс

2
@ gbjbaanb Я не думаю, що ти просиш мене пояснити, чому було б нерозумно мати два глобальних екземпляри Zork, назвати їх ZorkA та ZorkB та твердий код об'єктів, якими буде користуватися ZorkA, а хто буде використовувати ZorkB, правильно?
Майк Накіс

1
Я не говорив усім, я говорив майже всіх. Суть питання полягала в тому, чи має ДІ інші переваги, окрім заборони доступу до тих небагатьох акторів, які цього не потребують. Я знаю, що «об’єкти бога» є анти-зразком, але сліпо слідування найкращим практикам також може бути одним. Може статися, що вся мета програми - робити різні речі з певним ресурсом, у цьому випадку майже кожному потрібен доступ до цього ресурсу. Хорошим прикладом може бути програма, яка працює над зображенням: майже все, що в ній, має щось спільне з даними зображення.
vsz

1
@gbjbaanb Я думаю, що ідея полягає в тому, щоб один клієнтський клас міг працювати виключно над ZorkA або ZorkB, як вказано, а не щоб клієнтський клас вирішував, який із них захопити.
Євген Рябцев

39

Є безліч причин, чому непромінювані глобальні злі в ООП.

Це сумнівне твердження. Посилання використовується в якості доказу відноситься до державного - змінним глобал. Вони рішуче злі. Лише глобальні читання - це лише константи. Константи відносно здорові. Я маю на увазі, ви не збираєтеся вводити значення pi у всі свої класи, чи не так?

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

Ні, вони ні.

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

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

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

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

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

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

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


Вибачте за плутанину з "не-зміною", я хотів сказати, що змінюється, і якось не визнав моєї помилки.
vsz

«Це кошмар дизайну» - я знаю, що це таке. Але якщо вимоги полягають у тому, що всі ці події повинні бути здатні виконати зміни в даних, то події будуть пов'язані з даними, навіть якщо я розділяю їх і ховаю їх під шаром абстракцій. Вся програма про цих даних. Наприклад, якщо програмі належить виконати багато різних операцій над зображенням, то всі або майже всі її класи будуть якимось чином поєднані з даними зображення. Просто сказати "давайте написати програму, яка робить щось інше" не є прийнятною.
vsz

2
Програми, які мають багато об'єктів, які регулярно отримують доступ до якоїсь бази даних, не є абсолютно безпрецедентними.
Роберт Харві

@RobertHarvey - впевнений, але чи залежить від них інтерфейс "може отримати доступ до бази даних" чи "деякий постійний сховище даних для foos"?
Теластин

1
Нагадує мені про Ділберта, де PHB просить 500 функцій, а Ділберт каже ні. Тож PHB просить 1 функцію, і Ділберт каже впевнений. Наступного дня Ділберт отримує 500 запитів кожен за 1 функцію. Якщо щось не масштабується, воно просто не масштабується незалежно від того, як ви його одягаєте.
corsiKa

16

Є три основні причини, які слід враховувати.

  1. Читабельність. Якщо в кожній одиниці коду є все, що потрібно для роботи над введеним або переданим в якості параметрам, легко подивитися на код і миттєво побачити, що він робить. Це дає вам місце функціонування, що також дозволяє вам краще відокремити проблеми, а також змусить задуматися ...
  2. Модульність. Чому парсер повинен знати про весь перелік транспортних засобів та всі їх властивості? Мабуть, ні. Можливо, для цього потрібно запитати, чи існує ідентифікатор транспортного засобу, або чи має автомобіль X властивість Y. Чудово, це означає, що ви можете написати службу, яка це робить, і вставити лише цю послугу у свій аналізатор. Раптом у вас виходить дизайн, який має набагато більше сенсу, з кожним бітом коду обробляються лише ті дані, які йому відповідають. Що веде нас до ...
  3. Заповітність. Для типового тестового блоку ви хочете налаштувати своє середовище для багатьох різних сценаріїв, а введення робить це дуже простою у виконанні. Знову ж таки, повертаючись до прикладу аналізатора, чи справді ви хочете завжди складати весь список повноцінних транспортних засобів для кожного тестового випадку, який ви пишете для свого аналізатора? Або ви просто створили б макетну реалізацію вищезгаданого об'єкта служби, повертаючи різну кількість ідентифікаторів? Я знаю, яку вибрати.

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


+1 для стислої відповіді, присвяченої питанням технічного обслуговування. Більше P: відповіді SE повинні бути подібними до цього.
dodgethesteamroller

1
Ваша підсумка така гарна. Так добре. Якщо ви думаєте, що [модель дизайну місяця] або [модна місяць місяця] магічно вирішить ваші проблеми, вам буде погано провести час.
corsiKa

@corsiKa Я тривалий час був проти DI (або Spring, якщо бути точнішим), тому що я не бачив сенсу цього. Це просто здавалося жахливим клопотом без відчутного посилення (це було ще в дні конфігурації XML). Мені б хотілося, щоб я знайшов деяку документацію щодо того, що тоді насправді купує DI. Але я цього не зробив, тому мені довелося самій розібратися. Минуло багато часу. :)
biziclop

3

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

Так, так, це все ще глобально. Принципова причина його не змінилася, лише спосіб доступу до неї.

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

Незважаючи на все, що є прикладом "хвоста, що висить собакою", необхідність перевірки визначає те, як побудована база даних коду (подібні проблеми виникають з такими елементами, як статичні класи, наприклад, поточний час).

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


"Зазвичай глобальний об'єкт є дуже ретельним" - не, якщо він є змінним. На мій досвід, мати стільки змінних глобальних станів може бути кошмаром (хоча є способи зробити це нестерпним).
sleske

3

Окрім відповідей, які вже є,

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

Одна з характерних особливостей програмування OOP - зберігати лише властивості, які вам справді потрібні для конкретного об'єкта,

ЯКЩО ваш об’єкт має занадто багато властивостей - вам слід розбити об’єкт далі на суб'єкти.

Редагувати

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

Вам потрібно зробити тестування одиниць на графічному інтерфейсі графічного інтерфейсу Windows, якщо ви використовуєте глобальний, вам потрібно створити всі кнопки, і це, наприклад, для створення належного тестового випадку ...


Щоправда, але, наприклад, аналізатор повинен буде знати все, тому що може бути отримана будь-яка команда, яка може змінити будь-що.
vsz

1
"Перш ніж я прийти до вашого актуального питання" ... Ви збираєтесь відповісти на його запитання?
gbjbaanb

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