Деякі вважають, що шаблон Singleton - це завжди анти-візерунок. Що ти думаєш?
Деякі вважають, що шаблон Singleton - це завжди анти-візерунок. Що ти думаєш?
Відповіді:
Дві головні закиди Сінглтона потрапляють у два табори з того, що я спостерігав:
В результаті обох цих випадків загальним підходом є використання створення об'єкта широкого контейнера для зберігання єдиного екземпляра цих класів, і лише об'єкт контейнера змінює ці типи класів, тоді як багатьом іншим класам може бути наданий доступ до них для використання з об'єкт контейнера.
Я погоджуюсь, що це анти-модель. Чому? Оскільки це дозволяє вашому коду брехати про його залежності, і ви не можете довіряти іншим програмістам, щоб вони не вводили стан, що змінюється, у ваших незмінних сингтонах.
У класі може бути конструктор, який займає лише рядок, тому ви вважаєте, що він створений окремо і не має побічних ефектів. Однак, мовчки, він спілкується з якимось загальнодоступним, доступним у глобальному масштабі єдиним об'єктом, щоб кожен раз, коли ви інстанціювали клас, він містив різні дані. Це велика проблема не тільки для користувачів вашого API, але і для перевірки коду. Для правильного тестування коду потрібно мікро-керувати та бути обізнаним про глобальний стан у синглтоні, щоб отримати послідовні результати тестування.
Шаблон Singleton - це лише ліниво ініціалізована глобальна змінна. Глобальні змінні в цілому і справедливо вважаються злими, оскільки вони дозволяють страхітливі дії на відстані між, здавалося б, не пов'язаними частинами програми. Однак у IMHO немає нічого поганого з глобальними змінними, які встановлюються один раз, з одного місця, як частина програми ініціалізації програми (наприклад, за допомогою зчитування конфігураційного файла або аргументів командного рядка) і потім трактуються як константи. Таке використання глобальних змінних відрізняється лише буквою, а не духом, від того, щоб ідентифікаційна константа була оголошена під час компіляції.
Аналогічно, моя думка Сінглтонс полягає в тому, що вони погані, якщо і тільки в тому випадку, якщо вони використовуються для передачі змінного стану між, здавалося б, не пов'язаними частинами програми. Якщо вони не містять змінного стану або стан, що змінюється, він повністю інкапсульований, так що користувачі об'єкта не повинні знати про це навіть у багатопотоковому середовищі, то з ними нічого поганого немає.
Я бачив досить багато одинаків у світі PHP. Я не пам'ятаю жодного випадку використання, коли я вважав, що модель є виправданою. Але я думаю, що я отримав уявлення про мотивацію, чому люди його використовували.
Один екземпляр .
"Використовуйте один екземпляр класу C у всій програмі."
Це розумна вимога, наприклад, для "підключення до бази даних за замовчуванням". Це не означає, що ви ніколи не створите друге db-з'єднання, це просто означає, що ви зазвичай працюєте з типовим.
Єдина інстанція .
Msgstr "Не дозволяти копіювати клас C більше одного разу (за процес, за запитом тощо)."
Це актуально лише в тому випадку, якщо інстанціювання класу матиме побічні ефекти, що суперечать іншим екземплярам.
Часто цих конфліктів можна уникнути, переробивши компонент, наприклад, усунувши побічні ефекти від конструктора класу. Або їх можна вирішити іншими способами. Але все ж можуть бути легальні випадки використання.
Також слід подумати про те, чи дійсно вимога "лише одна" означає "одна на процес". Наприклад, для одночасності ресурсів, вимога є скоріше "одна на всю систему, через процеси", а не "одна на процес". А для інших речей це скоріше "контекст програми", і у вас просто трапляється один контекст програми на процес.
В іншому випадку не потрібно застосовувати це припущення. Забезпечення цього також означає, що ви не можете створити окремий екземпляр для одиничних тестів.
Глобальний доступ.
Це законно, лише якщо у вас немає належної інфраструктури для передачі об'єктів до місця, де вони використовуються. Це може означати, що ваша рамка чи середовище відстійні, але це може бути не в межах ваших повноважень виправити це.
Ціна - це щільна зв'язок, приховані залежності, і все, що погано стосується глобального стану. Але ти, мабуть, уже страждаєш від цього.
Ледача інстанція.
Це не є необхідною частиною синглів, але, здається, найпопулярніший спосіб їх реалізації. Але, хоча ліниві примірники - це приємна річ, то вам насправді не потрібен сингл, щоб досягти цього.
Типовою реалізацією є клас із приватним конструктором та статичною змінною екземпляра та методом статичного getInstance () з лінивою інстанцією.
На додаток до проблем, зазначених вище, це є одним із принципів єдиної відповідальності , оскільки клас керує власним обґрунтуванням та життєвим циклом , крім інших обов'язків, які вже мають клас.
У багатьох випадках можна досягти одного і того ж результату і без одиночного, і без глобального стану. Натомість слід використовувати введення залежності, і ви можете розглянути контейнер для ін'єкцій залежностей .
Однак є випадки використання, коли у вас залишаються такі дійсні вимоги:
Отже, ось що ви можете зробити в цьому випадку:
Створіть клас C, який потрібно інстанціювати, за допомогою загальнодоступного конструктора.
Створіть окремий клас S зі статичною змінною екземпляра та статичним методом S :: getInstance () з ледачим інстанцією, який буде використовувати клас C для екземпляра.
Виключіть усі побічні ефекти від конструктора C. Замість цього покладіть ці побічні ефекти у метод S :: getInstance ().
Якщо у вас є більше одного класу з вищезазначеними вимогами, ви можете розглянути можливість керування екземплярами класу з невеликим контейнером локальної служби та використовувати статичний екземпляр лише для контейнера. Отже, S :: getContainer () надасть вам лінивий контейнер служб, а ви отримаєте інші об'єкти з контейнера.
Уникайте дзвінків статичного getInstance (), де можна. Замість цього використовуйте ін'єкції залежності, коли це можливо. Тим більше, якщо ви використовуєте контейнерний підхід з декількома об'єктами, які залежать один від одного, тоді жоден із них не повинен викликати S :: getContainer ().
За бажанням створіть інтерфейс, який реалізує клас C, і використовуйте це для документування зворотного значення S :: getInstance ().
(Ми все ще називаємо це одинаком? Я залишаю це в розділі коментарів.)
Переваги:
Ви можете створити окремий екземпляр C для тестування одиниць, не торкаючись жодного глобального стану.
Управління екземплярами відокремлено від самого класу -> розділення проблем, принцип єдиної відповідальності.
Було б досить просто дозволити S :: getInstance () використовувати інший клас для екземпляра або навіть динамічно визначати, який клас використовувати.
Особисто я буду використовувати одиночні кнопки, коли мені потрібно 1, 2 або 3, або деяка обмежена кількість об'єктів для конкретного класу, про який йдеться. Або я хочу донести користувачеві мого класу, що я не хочу створити кілька примірників мого класу, щоб він нормально функціонував.
Також я буду використовувати його лише тоді, коли мені потрібно використовувати його майже скрізь у своєму коді, і я не хочу передавати об'єкт як параметр кожному класу або функції, яка йому потрібна.
Крім того, я буду використовувати синглтон, лише якщо він не порушує референтну прозорість іншої функції. Що означає деякий вхід, він завжди буде мати однаковий вихід. Тобто я не використовую це для глобальної держави. Якщо можливо, глобальний стан ініціалізується один раз і ніколи не змінюється.
Що стосується того, коли не використовувати його, дивіться вище 3 та змініть їх на протилежне.