Синглтон: Як слід його використовувати


291

Редагувати: З іншого питання я надіслав відповідь, що містить посилання на безліч питань / відповідей про одиночних клавіш.

Тож я прочитав нитку Singletons: хороший дизайн чи милицю?
І аргумент все ще вирує.

Я бачу Singletons як шаблон дизайну (хороший і поганий).

Проблема з Singleton - це не шаблон, а скоріше користувачів (вибачте всіх). Всі та їх батько думають, що вони можуть їх правильно виконати (і з багатьох інтерв'ю, які я зробив, більшість людей не можуть). Також тому, що всі думають, що можуть реалізувати правильний Singleton, вони зловживають Шаблоном і використовують його у ситуаціях, що не підходять (замінюючи глобальні змінні на Singletons!)

Тож основні питання, на які потрібно відповісти:

  • Коли ви повинні використовувати Singleton
  • Як правильно реалізувати Singleton

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


Тож підкачайте м'яч:
я підніму руку вгору і скажу, що це те, що я використовую, але, напевно, є проблеми.
Мені подобається, що "Скотт Майєрс" обробляє цю тему у своїх книгах "Ефективний C ++"

Хороші ситуації використовувати Singletons (не багато):

  • Рамки ведення журналів
  • Басейни для переробки ниток
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

ДОБРЕ. Давайте разом отримувати критику та інші реалізації.
:-)


36
Що робити, якщо згодом вирішите, що вам потрібно кілька реєстраторів? Або кілька пулів ниток? Якщо ви хочете лише одного реєстратора, то створіть лише один екземпляр і зробіть його глобальним. Синглтони хороші лише в тому випадку, якщо вам НЕОБХІДНЕ існувати лише одне, і НЕОБХІДНО бути глобальним, ІМХО.

3
Хто сказав, що рамка може мати лише 1 екземпляр реєстратора. Один сингелтон, що представляє Framework. Framwork може давати вам конкретні реєстратори.
Мартін Йорк

Так. Я б не використовував singeltong як нитку. Просто викидаючи ідеї, щоб викликати відповіді.
Мартін Йорк

@Dan Сінглтон, який реалізує схему стратегії. Поведінка абстрагується від одинакових. Singleton - це єдиний пункт входу. Не мати двох реєстраторів, мати один реєстратор, який може вирішити, як вести журнал. Ви не можете виводити лише один журнал одночасно, не потрібно мати два.
Лі Лув'є

5
Xaade: що робити, якщо ви хочете увійти до двох файлів? Або в базу даних? Або мережева розетка? Або віджет GUI? Справа в тому, що не додайте штучних обмежень - не потрібно. Як часто ви коли-небудь випадково створили дві для циклів, а не одну? Якщо ви хочете лише одного реєстратора, тоді створіть лише одного.

Відповіді:


181

Ви всі помиляєтесь. Прочитайте питання. Відповідь:

Використовуйте Singleton, якщо:

  • Потрібно мати один і лише один об'єкт типу в системі

Не використовуйте Singleton, якщо:

  • Ви хочете зберегти пам'ять
  • Ви хочете спробувати щось нове
  • Ви хочете показати, наскільки ви знаєте
  • Тому що всі інші це роблять (Дивіться вантажний культовий програміст у wikipedia)
  • У віджетах інтерфейсу користувача
  • Це повинен бути кеш
  • У струнах
  • У сесіях
  • Я можу ходити цілий день

Як створити найкращий синглтон:

  • Чим менше, тим краще. Я мінімаліст
  • Переконайтесь, що це безпечно для ниток
  • Переконайтесь, що це ніколи не є нульовим
  • Переконайтесь, що він створений лише один раз
  • Лінива чи ініціалізація системи? До ваших вимог
  • Іноді ОС або JVM створює для вас одиночні кнопки (наприклад, у Java кожне визначення класу є однотонним)
  • Надайте деструктора або якось зрозуміти, як розпоряджатися ресурсами
  • Використовуйте мало пам'яті

14
Насправді, я думаю, ви теж не зовсім правильні. Я б перефразував так: "Якщо вам потрібно мати один і лише один об'єкт типу в системі І вам потрібно мати глобальний доступ до нього" Наголос на потребі мій - не робіть цього, якщо це зручно, тільки якщо ви ОБОВ'ЯЗКОВО це мати.

91
Ви теж помиляєтесь. Якщо вам потрібен один і лише один об’єкт, ви створюєте один і єдиний. Якщо немає логічного способу, щоб два екземпляри могли бути розміщені без незворотнього пошкодження програми, слід подумати про те, щоб зробити його однократним. І тоді є інший аспект, глобальний доступ: Якщо вам не потрібен глобальний доступ до екземпляра, він не повинен бути синглтон.
jalf

4
Закрито для модифікації, відкрите для розширення. Проблема полягає в тому, що ви не можете розширити сингл на дуетон або триплетон. Він застряг, як сингл.
Лі Лув'є

2
@ enzom83: Сінглтон капіталу S включає код, щоб забезпечити його єдиність. Якщо ви хочете лише один екземпляр, ви можете втратити цей код і просто створити один екземпляр самостійно ... даючи вам економію пам’яті одного екземпляра, плюс економію від зменшення коду, що виконує єдину силу - що також означає не жертвувати можливість створити другий екземпляр, якщо колись зміниться ваші вимоги.
cHao

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

72

Синглтони дають можливість поєднувати дві погані риси в одному класі. Це майже неправильно в усіх відношеннях.

Сингл дає вам:

  1. Глобальний доступ до об'єкта та
  2. Гарантія, що не може бути створено більше ніж один об'єкт цього типу

Номер один прямолінійний. Глобали загалом погані. Ми ніколи не повинні робити об’єкти глобально доступними, якщо нам це справді не потрібно.

Номер два може здатися, що це має сенс, але давайте подумаємо про це. Коли ви востаннє ** випадково * створили новий об’єкт замість посилання на існуючий? Оскільки це позначено як C ++, давайте скористаємося прикладом з цієї мови. Ви часто випадково пишете

std::ostream os;
os << "hello world\n";

Коли ви мали намір писати

std::cout << "hello world\n";

Звичайно, ні. Нам не потрібен захист від цієї помилки, оскільки така помилка просто не відбувається. Якщо це так, правильна відповідь - піти додому і спати протягом 12-20 годин і сподіватися, що ви почуєте себе краще.

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

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

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

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

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

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


3
або ви можете зробити це глобальним і отримати лише один із недоліків сингтона. За допомогою сингтона ви одночасно обмежите себе одним екземпляром цього класу бази даних. Навіщо це робити? Або ви можете подивитися, чому у вас так багато залежностей, що список примірників стає "дійсно довгим". Чи всі вони потрібні? Чи слід делегувати деякі з них на інші компоненти? Можливо, деякі з них можуть бути упаковані в структуру, щоб ми могли передавати їх як єдиний аргумент. Рішень дуже багато, всі вони краще, ніж одиночні.
jalf

6
Так, сингл може бути виправданий там. Але я думаю, ви щойно довели мою думку, що це потрібно лише в досить екзотичних випадках. Більшість програмного забезпечення не займається технікою снігоочищення. Але я досі не переконаний. Я погоджуюсь, що у своїй дійсній заяві ви хочете лише одного із них. А як щодо ваших одиничних тестів? Кожен з них повинен працювати ізольовано, тому в ідеалі їм слід створити власний SpreaderController - що важко зробити з одинаком. Нарешті, чому б ваші колеги створювали кілька примірників у першу чергу? Це реалістичний сценарій захисту від?
jalf

3
І пункт, який ви пропустили, полягає в тому, що хоча два ваші останні приклади, безперечно, виправдовують обмеження "лише на один екземпляр", вони нічого не роблять для виправдання "глобально доступного". Чому на Землі повинна бути доступна вся кодова база, щоб отримати доступ до адміністративного блоку вашого телефонного комутатора? Сенс у синглтоні полягає у наданні вам обох рис. Якщо вам просто потрібен той чи інший, ви не повинні використовувати одиночку.
jalf

2
@ jalf - Моєю метою було лише навести вам приклад того, де Singleton корисний у дикій природі, оскільки ви не могли собі уявити жодного; Я думаю, ви не бачите багато разів, щоб застосувати його до своєї поточної роботи. Я перейшов на програмування снігоочисника з бізнес-додатків виключно тому, що це дозволило б мені використовувати Singleton. :) j / k Я погоджуюся з вашою передумовою про те, що існують кращі способи зробити це, ви дали мені багато що задуматися. Дякую за обговорення!
Дж. Полфер

2
Використання одинарного ( AHEM! ) " Шаблону " для запобігання встановленню більшої кількості екземплярів - це звичайна стара дурасть лише для того, щоб не допустити до людей прихильних дій. Коли у мене в моїй маленькій функції є локальна змінна foo1 типу Foo і я хочу лише одну функцію, я не переживаю, що хтось збирається створити другий Foo змінний foo2 і використовувати його замість оригінальної.
Томас Едінг

36

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

1) Сінглтони забезпечують глобальний механізм доступу до об'єкта. Хоча вони можуть бути незначно більш безпечними для потоків або гранично надійнішими в мовах без чітко визначеного порядку ініціалізації, це використання все ще є моральним еквівалентом глобальної змінної. Це глобальна змінна, одягнена в якийсь незграбний синтаксис (foo :: get_instance () замість g_foo, скажімо), але вона служить точно такій самій цілі (єдиний об'єкт, доступний у всій програмі) і має точно такі ж недоліки.

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


6
Домовились. Дві несправедливості можуть зробити право, на думку деяких, в реальному світі. Але в програмуванні змішування двох поганих ідей не призводить до хорошої.
jalf

27

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

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

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

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


8
Ні, сингтон - це набагато більше, ніж глобальна змінна в маскуванні. Саме це робить його особливо поганим. Він поєднує глобальність (що, як правило, погано) з іншою концепцією, яка також погана (концепція не давати програмісту інстанціювати клас, якщо він вирішить, що йому потрібен екземпляр) Вони часто використовуються як глобальні змінні. А потім вони перетягують і інший неприємний побічний ефект, і калікують кодову базу.
jalf

7
Слід також зазначити, що одиночні особи не повинні мати загальнодоступність. Однотонний дуже добре може бути внутрішнім у бібліотеці і ніколи не піддається користувачеві. Тому вони не обов'язково "глобальні" в цьому сенсі.
Стівен Еверс

1
+1, щоб вказати, наскільки сильно болить одиночні болі.
DevSolar

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

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

13

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

Java, зокрема, використовує одиночні кнопки як заміну глобальних змінних, оскільки все повинно міститися в класі. Найближчими до глобальних змінних є загальнодоступні статичні змінні, які можуть використовуватися так, ніби вони були глобальнимиimport static

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

Такі мови, як Python та Ruby, використовують одинаки дуже мало, тому що ви можете використовувати глобальні змінні в модулі.

Тож коли добре / погано користуватися одинаркою? Досить точно, коли було б добре / погано використовувати глобальну змінну.


Коли колись глобальна змінна "хороша"? Іноді вони є найкращим вирішенням проблеми, але вони ніколи не є "хорошими".
DevSolar

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

Мені подобається шар опосередкованості у цій відповіді: "коли було б добре / погано використовувати глобальний". І DevSolar, і Lee Louviere отримують цінність, з якою вони погоджуються, хоча у відповідь не могло бути відомо, хто буде коментувати.
Праксеоліт

6

Сучасний дизайн C ++ від Олександреску має універсальний однотонний надітний потік.

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


6
  • Як правильно реалізувати Singleton

Є одне питання, про яке я жодного разу не бачив, щось, з чим я стикався на попередній роботі. У нас були одиночні C ++, які ділилися між DLL, і звичайна механіка забезпечення єдиного примірника класу просто не працює. Проблема полягає в тому, що кожна DLL отримує власний набір статичних змінних разом з EXE. Якщо ваша функція get_instan є вбудованою або частиною статичної бібліотеки, кожна DLL завершиться власною копією "сингтона".

Рішення полягає в тому, щоб переконатися, що однотонний код визначений лише в одній DLL або EXE, або створити менеджер синглів з цими властивостями для розсилки екземплярів.


10
Йо дауг, я чув, що тобі сподобався Сінглтонс, тому я створив Сінглтон для вашого Сінглтона, тож ви можете робити анти-візерунок під час анти-шаблону.
Єва

@Eva, так щось подібне. Я не створював проблеми, я просто повинен був змусити її якось працювати.
Марк Рансом

5

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


Так, це зазначається у коментарях вище: * Обмеження: Один різьбовий дизайн * Див.: Aristeia.com/Papers/DDJ_Jul_Aug_2004_revid.pdf * Про проблеми, пов’язані з блокуванням у багатопотокових програмах
Martin York

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

навіть у c ++ 11 чи пізніших версіях?
hg_git

5

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

Деякі корисні аспекти синглів:

  1. ледачий або наперед випадок
  2. зручно для об’єкта, який потребує налаштування та / або стану

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

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


3

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

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


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

3

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

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


Самі лісоруби не повинні бути однотонними. Загальна система "мовлення" повідомлень повинна бути. Самі реєстратори є передплатниками широкомовних повідомлень.
CashCow

Нитки-пули також не повинні бути однотонними. Загальне питання - чи хочете ви хотіти більше одного з них? Так. Коли я востаннє використовував їх, у нас було 3 різних пули ниток в одному додатку.
CashCow

3

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


3

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



2

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

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

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


2
Це, безумовно, не краще. 1: Ви не визначаєте семантику власності за допомогою покажчика. Ніколи не слід використовувати покажчики в C ++, якщо ви не готові ними керувати. 2: Ваше використання подвійного перевіреного блокування є застарілим і є набагато кращі сучасні способи зробити це. 3: Ваші коментарі щодо знищення наївні. Рекультивація пам’яті - не сенс використання деструктора, а про очищення. Пропозиції щодо кращої версії: Подивіться на питання. Представлена ​​там версія вже набагато краща.
Мартін Йорк

1

Я використовую Singletons як тест на співбесіду.

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


45
Жорсткі та швидкі правила щодо найму змусять вас пропустити широке розмаїття потенційних працівників.
Карл

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

3
Що стосується книги рекордів - моя відповідь була язиком у щоку. У своєму фактичному процесі співбесіди я намагаюся оцінити, чи буде нам потрібно когось репетитора на C ++, і наскільки це буде важко. Деякі з моїх улюблених кандидатів - це люди, які НЕ знають C ++ всередині та поза, але мені вдалося провести чудову розмову з ними про це.
Метт Крейкшанк

4
Відхилення від голосування. З мого особистого досвіду - програміст може не назвати жодних інших моделей, окрім Singleton, але це не означає, що він використовує Singletons. Особисто я використовував одиночні кнопки у своєму коді, перш ніж я коли-небудь чув про них (я називав їх "розумнішими глобалами" - я знав, що таке глобальний). Коли я дізнався про них, коли дізнався про їх плюси та мінуси - я перестав їх використовувати. Раптом тестування для мене стало набагато цікавішим, коли я зупинився ... Чи це зробило мене гіршим програмістом? Пффф ...
Паулюс

3
Я також виступаю за дурницьке питання "назвіть деякі шаблони дизайну". Дизайн - це розуміння того, як застосовувати шаблони дизайну, а не просто мати змогу відмовитись від їх назв. Гаразд, це не може бути підставою для вступу, але ця відповідь є тролль-іш.
CashCow

0

Антивикористання:

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


Недопущені з 2 причин: 1. Singleton може внутрішньо використовувати поліморфні екземпляри (наприклад, глобальний Logger використовує поліморфні стратегії націлювання) 2. Може бути typedef для одиночного імені, тому код фактично залежить від typedef.
топовий гамедев

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

0

Я думаю, що це найнадійніша версія для C #:

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Ось оптимізована версія .NET :

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Ви можете знайти цю схему на dotfactory.com .


3
Ви можете викреслити частини, які не стосуються конкретних Singletons, щоб полегшити читання коду.
Мартін Йорк

Крім того, ваша перша версія не є безпечною для потоків через можливе впорядкування читання / запису. Див stackoverflow.com/questions/9666 / ...
Thomas Danecker

5
Ага ... неправильна мова? Питання досить очевидно з тегом C ++ .
DevSolar

0

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

Синглтон - це реалізація для глобально доступного об'єкта (GAO відтепер), хоча не всі GAO є однотонними.

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

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

Зважаючи на це, КЗП можна ініціалізувати так:

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

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

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


0

Я досі не розумію, чому сингл має бути глобальним.

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

Я не бачу, чому ця функціональність була б поганою.


Я не розумію, чому ви вважаєте, що це має бути глобальним.
Мартін Йорк

згідно з цією темою, всі говорили, що сингл має бути глобальним
Захарій Краус

1
Ні. Нитка вказує на те, що сингелтон має глобальний стан. Не те, щоб це глобальна змінна. Пропоноване вами рішення має глобальний стан. Пропоноване вами рішення - це також використання глобальної змінної; статичний член класу є об'єктом "Статичної тривалості зберігання", а глобальна змінна є об'єктом "Статична тривалість зберігання". Таким чином, це в основному те саме, що має дещо різну семантику / сферу застосування.
Мартін Йорк

Тож приватна статична змінна все ще є глобальною через "Тривалість статичного зберігання"?
Захарій Краус

1
Примітка: Ви пропустили мій свідомо жоден заявлений біт. Ваш дизайн використання статичного "приватного" члена не поганий так само, як сингелтон. Тому що це не вводить "глобальний стан, що змінюється". Але це теж не сингтон. Синглтон - це клас, розроблений таким чином, щоб існував лише один екземпляр об'єкта. Що ви пропонуєте - це єдиний загальний стан для всіх об'єктів класу. Різне поняття.
Мартін Йорк

0

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

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


0

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

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


-1

У додатках для настільних комп’ютерів (я знаю, що ми пишемо це лише ми, динозаври!), Вони дуже важливі для отримання відносно незмінних глобальних налаштувань додатків - мови користувача, шляху до файлів довідки, налаштувань користувачів тощо, які в іншому випадку повинні пропонуватись для кожного класу та кожного діалогу .

Редагувати - звичайно, вони повинні бути лише для читання!


Але це ставить питання; чому мова користувача і шлях до файлу довідки повинні бути випадки , метод взагалі ?
DrPizza

2
Для цього у нас є глобалісти. Не потрібно робити їх
одинаками

Глобальні змінні - тоді як ви їх серіалізуєте з реєстру / бази даних? Гобальський клас - то як ти гарантуєш, що є лише один із них?
Мартін Бекетт

@mgb: ви їх серіалізуєте, читаючи значення з реєстру / бази даних та зберігаючи їх у глобальних змінних (це, мабуть, слід зробити у верхній частині вашої основної функції). ви гарантуєте, що існує лише один об'єкт класу, створивши лише один об’єкт класу ... дійсно ... важко "grep -rn" нове \ + global_class_name "." ? справді?
Паулюс

7
@mgb: Чому на Землі я б переконався, що існує лише один? Мені просто потрібно знати, що один екземпляр завжди представляє поточні налаштування. але немає ніяких причин, чому мені не дозволяти мати об’єкти інших налаштувань навколо місця. Можливо, наприклад, для "налаштувань, які користувач наразі визначає, але ще не застосував", наприклад. Або один для "конфігурації, яку користувач зберігав раніше, щоб він міг повернутися до них пізніше". Або по одному для кожного вашого тесту.
jalf

-1

Ще одна реалізація

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};

3
Це справді жахливо. Дивіться: stackoverflow.com/questions/1008019/c-singleton-design-pattern/… для кращої лінивої ініціалізації та важливішого гарантування детермінованого знищення.
Мартін Йорк

Якщо ви збираєтесь використовувати покажчики, Instance()слід повернути вказівник, а не посилання. Всередині вашого .cppфайлу ініціалізації примірника пусте значення: Singleton* Singleton::instance_ = nullptr;. І Instance()повинні бути реалізовані в вигляді: if (instance_ == nullptr) instance_ = new Singleton(); return instance_;.
Денніс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.