Що стосується моделей дизайну: Коли я повинен використовувати одиночний?


448

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

Надайте мені сценарії, окрім старого хорошого лісоруба, де є сенс використовувати синглтон.


4
Оскільки я вивчаю ерланг, я віддаю перевагу такому підходу, а саме незмінності та передачі повідомлень.
Сеторі

209
Що не є конструктивним у цьому питанні? Я бачу конструктивну відповідь нижче.
mk12

3
Рамка введення залежності - це дуже складний сингтон, який видає об'єкт….
Ян Рінроуз

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

У мене є побічне запитання: будь-яка реалізація Singleton також може бути реалізована за допомогою "статичного" класу (методом "factory" / "init"), не створюючи фактично екземпляр класу (можна сказати, що статичний клас - це вид реалізації Singleton, але ...) - чому слід використовувати фактичний Singleton (єдиний екземпляр класу, що забезпечує його єдиний) замість статичного класу? Єдина причина, про яку я можу подумати, - це може бути "семантика", але навіть у цьому сенсі випадки використання Singleton насправді не вимагають "визначення класу> екземпляра" за визначенням ... так ... чому?
Ювал А.

Відповіді:


358

На моєму прагненні до правди я виявив, що насправді дуже мало "прийнятних" причин використовувати Singleton.

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

Ведення журналів - конкретний приклад "прийнятного" Singleton, оскільки він не впливає на виконання вашого коду. Вимкнути журнал, виконання коду залишається колишнім. Увімкніть це, те саме. Місько викладає це так у Root Cause of Singletons : "Інформація тут протікає в один бік: з вашої програми в реєстратор. Навіть незважаючи на те, що реєстратори є глобальним станом, оскільки жодна інформація не перетікає з реєстраторів у вашу програму, реєстратори є прийнятними".

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

Детальніше читайте на Singleton Я тебе люблю, але ти мене зводиш.


3
@ArneMertz Я здогадуюсь, це саме той.
Атакуючий

1
Чому ви не можете просто використовувати глобальний об’єкт? Чому це повинно бути одиноким?
взуття

1
Я думаю, статичний метод для утиліти журналу?
Скайнет

1
Синглтони найкраще, коли вам потрібно керувати ресурсами. Наприклад, Http-з'єднання. Ви не хочете встановлювати 1 мільйон http-клієнтів для одного клієнта, це шалено марнотратно і повільно. Таким чином, одиночний з підключеним http-клієнтом з'єднання буде набагато швидшим та зручним для ресурсів.
Cogman

3
Я знаю, що це старе питання, і інформація в цій відповіді чудова. Однак у мене виникають проблеми з розумінням того, чому це прийнята відповідь, коли в ОП чітко визначено: "Дайте мені сценарії, окрім старого доброго реєстратора, де є сенс використовувати синглтон".
Франсіско К.

124

Кандидат в одиночку повинен відповідати трьом вимогам:

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

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

Наприклад, навряд чи зателефонують із спілера принтера з більш ніж одного місця (меню «Друк»), тому ви можете використовувати мутекси для вирішення паралельної проблеми доступу.

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


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

15
Я думаю, ви пропустили слово "кандидат". Кандидат в одиночку повинен відповідати трьом вимогам; тільки те, що щось відповідає вимогам, не означає, що це має бути Singleton. Можуть бути й інші фактори дизайну :)
metao

45

Читання файлів конфігурації, які слід читати лише під час запуску, та інкапсулюючи їх у Singleton.


8
Схожий на Properties.Settings.Default.NET.
Нік Бедфорд

9
@Paul, "Табір без одиночки" зазначає, що об'єкт конфігурації повинен просто бути переданий у функції, які йому потрібні, а не робити його глобально доступним (він же однотонний).
Pacerier

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

3
@PaulCroarkin Чи можете ви розширити це та пояснити, наскільки це вигідно?
AlexG

1
@ rr - якщо конфігурація переміститься до бази даних, вона все одно може бути інкапсульована в об’єкт конфігурації, який буде переданий у функції, які її потребують. (PS Я не в таборі "без одиночок").
Буде Шеппард

36

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

Або підключення до бази даних, або файловий менеджер тощо.


30
Я чув приклад цього спілера принтера, і думаю, що це кульга. Хто каже, що я не можу мати більше ніж один спойлер? Що за чортів спітер принтера? Що робити, якщо у мене різні принтери, які не можуть конфліктувати або використовувати різні драйвери?
1800 р. ІНФОРМАЦІЯ

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

2
Це класичний приклад для банди чотирьох. Я думаю, що відповідь із справжнім випробуваним випадком використання буде кориснішою. Я маю на увазі ситуацію, коли ви насправді відчували, що Сінглтон - найкраще рішення.
Андрій Важна II

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

Що за чортівня - це спікер принтера?
RayLoveless

23

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


4
Мова користувача може бути однотонною з припущенням, що системою може користуватися лише один користувач.
Самуель Ослунд

… І що один користувач розмовляє лише однією мовою.
спектри

17

Управління з'єднанням (або пулом з'єднань) до бази даних.

Я також використовував би його для отримання та зберігання інформації у зовнішніх файлах конфігурації.


2
Чи не був би генератор підключення до бази даних прикладом заводу?
Кен

3
@Ken, ви б хотіли, щоб ця фабрика була одинаковою майже у всіх випадках.
Кріс Марісіч

2
@Federico, "Табір без одинаків" зазначає, що ці підключення до бази даних повинні просто переходити у функції, які їм потрібні, а не робити їх глобально доступними (він же однотонний).
Pacerier

3
Вам для цього справді не потрібен сингл. Його можна вводити ін’єкційно.
Нестор Ледон

11

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

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


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

10

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

Використовуючи Singletons, ви повинні переконатися, що ви випадково не приховуєте залежності. В ідеалі одиночні кнопки (як і більшість статичних змінних у додатку) встановлюються під час виконання коду ініціалізації для програми (статичний недійсний Main () для виконуваних файлів C #, статичний недійсний main () для виконуваних файлів Java) і потім передаються в всі інші класи, які вимагають цього. Це допомагає вам зберегти заповітність.


8

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


6

Практичний приклад синглтону можна знайти в Test :: Builder , класі, який підтримує практично кожен сучасний модуль тестування Perl. Сингл-тест Test :: Builder зберігає та брокерів стан та історію тестового процесу (історичні результати тестування, підраховує кількість запущених тестів), а також такі речі, як, куди йде тестовий результат. Це все необхідне для узгодження декількох модулів тестування, написаних різними авторами, для спільної роботи в одному тестовому сценарії.

Історія тесту :: Одногранник будівельника є навчальним. Виклик new()завжди дає вам один і той же об’єкт. По-перше, всі дані зберігалися у вигляді змінних класів, що нічого не має в самому об'єкті. Це працювало, поки я не хотів перевірити Test :: Builder із самим собою. Тоді мені знадобилися два об’єкти Test :: Builder, одна установка як манекен, щоб захопити та перевірити його поведінку та вихід, а один - справжній тестовий об’єкт. У цей момент Test :: Builder був перероблений на реальний об'єкт. Одиничний об'єкт зберігався як дані класу і new()завжди повертав його. create()було додано, щоб зробити свіжий об'єкт і включити тестування.

В даний час користувачі хочуть змінити деякі поведінки Test :: Builder у своєму модулі, але інші залишити в спокої, в той час як історія тестів залишається спільною для всіх модулів тестування. Зараз відбувається монолітний об'єкт Test :: Builder, який розбивається на більш дрібні фрагменти (історія, вихід, формат ...) за допомогою екземпляра Test :: Builder, який збирає їх разом. Тепер тест :: Builder більше не повинен бути одиноким. Його компоненти, як і історія, можуть бути. Це висуває невгамовну необхідність сингла до рівня. Це надає більшої гнучкості користувачеві для змішування та співпадіння творів. Менші об'єкти одиночної форми тепер можуть просто зберігати дані, і їх об'єкти, що містять, вирішують, як їх використовувати. Він навіть дозволяє не тестувати :: Builder клас, щоб грати разом, використовуючи тест: :: Історія будівельника та виведення одиночних клавіш.

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


5

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


2
Чому б ви не просто завантажували дані один раз і передавали об’єкт конфігурації за потребою?
lagweezle

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

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

@spectras Чи повинен я? Якщо я реалізую діалог із gui, мені знадобляться: сховище, локалізація, дані сеансу, дані програми, батьківський віджет, клієнтські дані, менеджер дозволів та, можливо, багато іншого. Звичайно, ви можете зібрати деякі, але чому? Особисто я використовую весну та аспекти, щоб просто передати всі ці залежності в клас віджетів, і це деакупує все.
Enerccio

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

3

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

Один із конкретних прикладів, які я бачив, - це автор записів Lucene Search Index.


1
І все ж, IndexWriter - це не сингл ...
Марк

3

Ви можете використовувати Singleton при реалізації схеми State (таким чином, як показано в книзі GoF). Це пояснюється тим, що конкретні державні класи не мають власного стану і виконують свої дії з точки зору контекстного класу.

Ви також можете зробити абстрактний завод однотонним.


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

1
@ kiwicomb123 Спробуйте домогтися setState()відповідальності за прийняття політики щодо створення держави. Це допомагає, якщо ваша мова програмування підтримує шаблони або дженерики. Замість Singleton можна використовувати шаблон Monostate , коли інстанціювання об'єкта стану закінчується повторним використанням того ж глобального / статичного об'єкта стану. Синтаксис зміни стану може залишатися незмінним, оскільки ваші користувачі не повинні знати, що інстанційований стан є Monostate.
Еміль Корм'є

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

@ kiwicomb123 Ні, Monostate не має стати всіх членів статичними. Краще, щоб ви прочитали на ньому, а потім перевірте, чи немає відповідних питань та відповідей.
Еміль Корм'є

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

3

Спільні ресурси Особливо для PHP, класу баз даних, шаблонного класу та глобального класу змінних депо. Всі вони повинні бути спільними для всіх модулів / класів, які використовуються протягом коду.

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

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

$gb->config['hostname']

або з усіма мовними значеннями в масиві, наприклад:

$gb->lang['ENTER_USER']

Зрештою, запустивши код сторінки, ви отримаєте, скажімо, вже зрілий:

$template

Синглтон, $gbсинглтон, який має масив lang для заміни в нього, і весь вихід завантажений і готовий. Ви просто замінюєте їх на ключі, які зараз є у значенні сторінки об’єкта зрілих шаблонів, а потім подаєте їх користувачеві.

Велика перевага цього в тому, що ви можете робити будь-яку вподобану післяобробку будь-що. Ви можете передати всі мовні значення для перекладу google або іншої служби перекладу та отримати їх назад, а також замінити їх на місця, перекладені, наприклад. або, ви можете замінити в структурах сторінок або


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

1

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

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


0

Я використовую його для об'єкта параметрів командного рядка, що інкапсулює, коли працює з модулями, що підключаються. Основна програма не знає, які параметри командного рядка для модулів, які завантажуються (і не завжди навіть знає, які модулі завантажуються). наприклад, основні навантаження A, яким не потрібні самі параметри (тому чому він повинен займати додатковий вказівник / посилання / що завгодно, я не впевнений - схоже на забруднення), потім завантажує модулі X, Y і Z. Два з них, скажімо, X і Z, потрібні (або прийняти) параметри, тому вони передзвонюють в командний рядок сингтон, щоб повідомити, які параметри приймати, і під час виконання вони передзвонюють, щоб дізнатися, чи дійсно користувач вказав якісь їх.

Багато в чому синглтон для обробки параметрів CGI працював би так само, якщо ви використовуєте лише один процес за запитом (інші методи mod_ * цього не роблять, тому це буде погано - таким чином, аргумент, який говорить, що ви не повинні ' t використовувати одиночні кнопки у світі mod_cgi у випадку, якщо ви портуєте до mod_perl або будь-якого іншого світу).


-1

Можливо, приклад з кодом.

Тут ConcreteRegistry - сингл-покер у грі в покер, який дозволяє поведінці аж до дерева пакетів отримати доступ до декількох основних основних інтерфейсів гри (тобто, фасадів для моделі, виду, контролера, оточення тощо):

http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html

Ред.


1
Зараз посилання розірвано, але якщо ви реєструєте інформацію про перегляд в одиночному кнопці, до якого можна отримати доступ у всій програмі, вам не вистачає точки MVC. Погляд оновлюється (і повідомляється з ним) контролером, який використовує модель. Як це звучить тут, мабуть, неправильне використання Singleton, і рефакторинг в порядку.
drharris

-9

1 - коментар до першої відповіді:

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

2 - Я намагаюся не створювати синглів вручну. Я просто створюю простий об'єкт з конструкторами, які дозволяють мені вводити в об’єкт співпрацівників. Якщо мені знадобився синглтон, я б використовував рамку огляду залежностей (Spring.NET, Unity для .NET, Spring для Java) або якусь іншу.


11
Ви повинні коментувати відповіді безпосередньо, натиснувши на посилання внизу відповіді; набагато простіше читати таким чином. Крім того, відповідь, яку ви побачили вгорі, мабуть, не перша. Відповіді постійно впорядковуються.
Росс

чому ви хочете провести тестовий журнал?
Enerccio

Існує величезна різниця між "статичним класом Logger" та статичним екземпляром Logger . Однотонний візерунок не говорить "зробіть свій клас статичним", він говорить про доступ до екземпляра об'єкта статичним. Так, наприклад, ILogger logger = Logger.SingleInstance();де цей метод є статичним і він повертає статично збережений екземпляр ILogger. Ви використовували приклад "структури введення залежності". Майже всі контейнери DI є однотонними; їх конфігурації визначаються статично і в кінцевому підсумку доступні / зберігаються в одному інтерфейсі постачальника послуг.
Джон Девіс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.