Якщо одиночні люди погані, то чому контейнер послуг хороший?


91

Ми всі знаємо, наскільки погані самотні , тому що вони приховують залежності та з інших причин .

Але у фреймворку може бути багато об’єктів, які потрібно створити лише один раз і викликати їх звідусіль (реєстратор, db тощо).

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

Але чому постачальник послуг не такий поганий, як чистий синглтон?

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

PS. Я знаю, що, щоб не приховувати залежностей, я повинен використовувати DI (як зазначив Місько)

Додати

Я б додав: У наші дні одинокі не такі вже й злі, це пояснив творець PHPUnit:

DI + Singleton вирішує проблему:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>

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

Крім DI та Service Container, чи є якесь прийнятне рішення для доступу до цих допоміжних об'єктів?


2
@yes Ваша редакція робить помилкові припущення. Себастьян жодним чином не припускає, що фрагмент коду робить використання Singleons меншою проблемою. Це лише один із способів зробити код, який інакше було б неможливо перевірити більш перевіреним. Але це все ще проблематичний код. Насправді він прямо зазначає: "Те, що ти можеш, не означає, що ти повинен". Правильним рішенням все-таки було б взагалі не використовувати Singletons.
Гордон,

3
@yes дотримуються принципу SOLID.
Гордон,

19
Я заперечую твердження про те, що одиночки погані. Їм можна зловживати, так, але будь-яким інструментом. Скальпелем можна врятувати життя або покінчити з ним. Бензопила може розчищати ліси, щоб запобігти пожежам, або відрізати значну частину вашої руки, якщо ви не знаєте, що робите. Навчіться грамотно користуватися своїми інструментами і не сприймати поради як євангелію - так лежить немислячий розум.
paxdiablo

4
@paxdiablo , але вони є поганими. Сінглтон порушує SRP, OCP та DIP. Вони вводять у ваш додаток глобальний стан і щільне зв’язування, і ваш API буде брехати про свої залежності. Все це негативно вплине на ремонтопридатність, зручність читання та перевірку вашого коду. Можуть бути рідкісні випадки, коли ці недоліки перевищують незначні переваги, але я стверджую, що в 99% вам не потрібен синглтон. Особливо в PHP, де Singleton в будь-якому випадку є унікальним для Запиту, а збирати графіки співавторів із Builder просто.
Гордон,

5
Ні, я не вірю. Інструмент - це засіб для виконання функції, як правило, полегшуючи це якось, хоча деякі (emacs?) Мають рідкісну відмінність, що ускладнює :-) У цьому синглтон нічим не відрізняється від збалансованого дерева або компілятора . Якщо вам потрібно забезпечити лише одну копію об’єкта, це робить синглтон. Якщо він робить це добре можна обговорювати , але я не вірю , що можна стверджувати , що він не робить це взагалі. І можуть бути кращі способи, наприклад, бензопила швидше ручної, або цвяховий пістолет проти молотка. Це не робить ручну пилку / молоток меншим інструментом.
paxdiablo

Відповіді:


76

Локатор послуг - це, як можна сказати, лише одне з двох зол. "Менший" зводиться до цих чотирьох відмінностей ( принаймні я зараз не можу думати про жодні інші ):

Принцип єдиної відповідальності

Контейнер послуг не порушує Принцип єдиної відповідальності, як це робить Singleton. Сінглтони поєднують створення об’єктів та бізнес-логіку, тоді як Службовий контейнер несе сувору відповідальність за управління життєвим циклом об’єкта вашої програми. У цьому відношенні Контейнер послуг кращий.

Зчеплення

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

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

Приховані залежності

Проблема приховування залежностей існує дуже багато. Коли ви просто вводите локатор у ваші споживаючі класи, ви не знаєте ніяких залежностей. Але на відміну від Сінглтона, SL зазвичай створює всі необхідні кулуарні залежності. Отже, коли ви отримуєте Службу, ви не опиняєтесь , як Місько Хевері в прикладі CreditCard , наприклад, вам не доведеться створювати екземпляри всіх депеденцій залежностей вручну.

Отримання залежностей всередині екземпляра також порушує Закон Деметри , який говорить, що не слід копатись у співавторах. Екземпляр повинен говорити лише зі своїми безпосередніми співробітниками. Це проблема як із Singleton, так і з ServiceLocator.

Глобальна держава

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

Також дивіться Фаулера про Службу пошуку локаторів проти введення залежностей для набагато більш глибокого обговорення.


Примітка до вашого оновлення та пов’язана стаття Себастьяна Бергмана про тестування коду, який використовує Singletons : Себастьян жодним чином не припускає, що запропонований обхідний шлях робить використання Singleons меншою проблемою. Це лише один із способів зробити код, який інакше було б неможливо перевірити більш перевіреним. Але це все ще проблематичний код. Насправді він прямо зазначає: "Те, що ти можеш, не означає, що ти повинен".


1
Особливо тут слід забезпечити тестування. Ви не можете глузувати із викликів статичних методів. Однак ви можете знущатися над службами, які були введені через конструктор або сеттер.
Девід

44

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

Отже, ваше запитання: чому локатори послуг хороші? Моя відповідь така: вони ні.

Уникайте, уникайте, уникайте.


6
Схоже, ви нічого не знаєте про інтерфейси. Клас просто описує необхідний інтерфейс у підписі конструктора - і це все, що він повинен знати. Пройдений службовий локатор повинен реалізувати інтерфейс, і все. І якщо IDE перевірить реалізацію інтерфейсу, контролювати будь-які зміни буде досить просто.
OZ_

4
@ yes123: Люди, які кажуть, що вони помиляються, і вони помиляються, тому що SL є анти-шаблоном. Ваше питання: "чому SL хороший?" Моя відповідь така: вони ні.
jason

5
Я не буду сперечатися, чи є SL аніт-зразком чи ні, але я скажу, що це набагато менше зло в порівнянні з одиночним і глобальним. Ви не можете перевірити клас, який залежить від синглтона, але ви точно можете перевірити клас, який залежить від SL (albiet, ви можете зіпсувати дизайн SL до такої міри, що він не працює) ... Так що це варто відзначаючи ...
ircmaxell

3
@Jason, вам потрібно передати об'єкт, який реалізує інтерфейс - і це лише те, що вам потрібно знати. Ви обмежуєтесь лише визначенням конструктора класів і хочете написати в конструкторі всі класи (а не інтерфейси) - дурна ідея. Все, що вам потрібно, це Інтерфейс. Ви можете успішно протестувати цей клас з макетами, ви можете легко змінити поведінку, не змінюючи код, немає зайвих залежностей та зчеплення - це все (загалом), що ми хочемо мати в Dependency Injection.
OZ_

2
Звичайно, я просто з’єднаю Базу даних, Журнал реєстрації, Диск, Шаблон, Кеш-пам’ять та Користувача в один об’єкт «Введення», безумовно, буде простіше визначити, на які залежності покладається мій об’єкт, ніж якби я використовував контейнер.
Mahn

4

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

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

Це гарне запитання в SO, що пояснює різницю в обох: Яка різниця між моделями введення залежностей та шаблонами служб локатора?


"DIC вводить відповідні залежності для вас" Хіба це не трапляється і з Singleton?
динамічний

5
@ yes123 - Якщо ви використовуєте синглтон, ви б не вводили його, у більшості випадків ви просто отримували б до нього доступ у всьому світі (це суть синглтона). Я гадаю, якщо ви скажете, що якщо ви введете синглтон, це не буде приховувати залежностей, але це начебто перемагає початкову мету шаблону Сінглтона - ви запитаєте себе, якщо мені не потрібен доступ до цього класу в усьому світі, чому мені потрібно зробити це Singleton?
rickchristie

2

Оскільки ви можете легко замінити об'єкти в Service Container на
1) успадкування (клас Object Manager можна успадкувати, а методи можна замінити)
2) зміна конфігурації (у випадку з Symfony)

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

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


0

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

Наприклад, у мене є інтерфейс і адміністратор. Усередині адміністратора я хочу, щоб вони могли входити як користувач. Розглянемо код всередині адміністратора.

$frontend = new Frontend();
$frontend->auth->login($_GET['user']);
$frontend->redirect('/');

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

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

$logger1=$api->add('Logger');
$logger2=$api->add('Logger');

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

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


1
отже, ваш метод - передавати $api var навколо вашого фреймворку? Я точно не зрозумів, що ти маєш на увазі. Крім того, якщо виклик add('Logger')повертає той самий екземпляр, в основному у вас є службовий контейнер
динамічний

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