Тож одиночні погані, то що?


553

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

Ось справжній приклад головного недавнього проекту, в якому я брав участь:

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

Чи є тут якийсь інший підхід, ніж в основному такий об’єкт кешу глобального керування даними? Офіційно цей об'єкт не повинен бути "одиночним", але в концептуальному сенсі він має бути таким. Яка приємна чиста альтернатива тут?


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

14
@Anon: Як використання статичного класу покращує ситуацію. Є ще щільна муфта?
Мартін Йорк

5
@Martin: Я не припускаю, що це робить "кращим". Я припускаю, що в більшості випадків сингл - це рішення в пошуках проблеми.
Анон.

9
@Anon: Неправда. Статичні класи дають вам (майже) контроль над інстанцією та ускладнюють багатопотоковість ще складніше, ніж це роблять Singletons (оскільки вам доведеться серіалізувати доступ до кожного окремого методу, а не лише до примірника). Singletons також може принаймні реалізувати інтерфейс, який статичні класи не можуть. Статичні класи, безумовно, мають свої переваги, але в цьому випадку Сінглтон, безумовно, менший з двох значних зл. Статичний клас, який реалізує будь-який стан, що змінюється, як великий миготливий неоновий "ПОПЕРЕДЖЕННЯ: BAD DESIGN AHEAD!" знак.
Aaronaught

7
@Aaronaught: Якщо ви просто синхронізуєте доступ до одиночного, то ваша паралельність порушена . Ваш потік може бути перерваний одразу після отримання одиночного об'єкта, з'являється інша нитка, і звинувачуйте, перегонившись. Використання Singleton замість статичного класу в більшості випадків - це просто зняття попереджувальних знаків і мислення, що вирішує проблему .
Анон.

Відповіді:


809

Тут важливо розрізняти окремі екземпляри та шаблон дизайну Singleton .

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

Шаблон дизайну Singleton - це дуже специфічний тип одиничного екземпляра, зокрема такий:

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

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

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

Жоден із цих симптомів насправді не є ендемічним для одиничних випадків, лише закономірність Сінглтона.

Що ти можеш зробити замість цього? Просто не використовуйте шаблон Singleton.

Цитуючи з питання:

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

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

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

Перше, що я зробив би - це розділити різні функціональні області кешу на окремі інтерфейси. Наприклад, скажімо, ви робили найгірший у світі клон YouTube на основі Microsoft Access:

                          MSAccessCache
                                ▲
                                |
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

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

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

Навіщо використовувати інтерфейс на основі інтерфейсу? Три причини:

  1. Це полегшує читання коду; ви можете чітко зрозуміти з інтерфейсів, від яких даних залежать залежні класи.

  2. Якщо і коли ви зрозумієте, що Microsoft Access не був найкращим вибором для резервного копіювання даних, можете замінити його чимось кращим - скажімо, SQL Server.

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

Якщо ви хочете зробити це на крок далі, ви можете використовувати контейнер IoC (рамки DI), наприклад Spring (Java) або Unity (.NET). Практично кожен фреймворк DI буде робити своє власне управління життям і конкретно дозволяє вам визначити певну послугу як єдиний екземпляр (часто називаючи це "синглтон", але це лише для ознайомлення). В основному ці рамки економлять більшу частину роботи мавпи вручну проходження екземплярів, але вони не є суворо необхідними. Для втілення цієї конструкції вам не потрібні спеціальні інструменти.

Для повноти слід зазначити, що вищезазначений дизайн насправді теж не ідеальний. Коли ви маєте справу з кешем (як і ви), ви насправді повинні мати зовсім окремий шар . Іншими словами, такий дизайн:

                                                        + - IMediaRepository
                                                        |
                          Кеш (загальний) --------------- + - IProfileRepository
                                ▲ |
                                | + - IPageRepository
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

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

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

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


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

4
Чому Кеш не може бути одиноким? Чи це не сингл, якщо передати його та застосувати ін'єкцію залежності? Сінглтон - це лише обмеження себе одним екземпляром, а не про те, як це доступно правильно? Дивіться, я приймаю це: assoc.tumblr.com/post/51302471844/the-misunderposed-singleton
Ерік Енгхайм

29
@AdamSmith: Ви насправді читали будь-яку з цієї відповіді? На ваше запитання відповідають у перших двох абзацах. Singleton Pattern! == Один екземпляр.
Aaronaught

5
@Cawas та Adam Smith - Читаючи ваші посилання, я відчуваю, що ви справді не прочитали цю відповідь - все вже є.
Вільберт

19
@Cawas Я відчуваю, що м'ясо цієї відповіді - це відмінність між одномоментним та одинарним. Singleton - це погано, одномоментний - ні. Dependency Injection - це приємний, загальний спосіб використання одиничних екземплярів без використання синглів.
Вільберт

48

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

Чому екрани запитують об’єкт кешу даних? Кешування має бути прозорим для клієнта. Має бути відповідна абстракція для надання даних, і реалізація цієї абстракції може використовувати кешування.

Можливо, проблема між частинами системи встановлена ​​неправильно, і це, мабуть, системно.

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

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

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

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

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

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

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


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

1
<cont> Він по суті прозорий. У тому сенсі, що відбувається зворотний дзвінок з сервера, якщо певні необхідні дані ще не кешовані. Але реалізація цього кеш-менеджера (логічно та фізично) є Singleton.
Столи Бобі

6
Я з qstarin тут: Об'єкти, що отримують доступ до даних, не повинні знати (або потрібно знати), що дані кешовані (тобто деталі реалізації). Користувачі даних просто запитують дані (або запитують інтерфейс для отримання даних).
Мартін Йорк

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

2
@Bobby Tables: тоді ваша ситуація не така страшна, як здавалася. Цей Сінглтон (якщо припустити, що ви маєте на увазі статичний клас, а не просто об’єкт із екземпляром, який живе до тих пір, як додаток), як і раніше, є проблематичним. Це приховує той факт, що ваш об'єкт, що надає дані, залежить від постачальника кешу. Краще, якщо це буде явним і зовнішнім. Розв’яжіть їх. Для перевірки важливо, щоб ви могли легко замінити компоненти, і провайдер кеша є яскравим прикладом такого компонента (як часто постачальник кеш-пам'яті підтримується ASP.Net).
квентин-зірин

45

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

tl; dr:

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

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

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

  • Викопайте його цілком. Я бачив безліч класів одиночних, які не мають штату і є лише мішками допоміжних функцій. Їм взагалі не потрібен екземпляр. Просто зробіть їх статичними функціями або перенесіть їх в один із класів, який функція приймає за аргумент. Вам би не потрібен спеціальний Mathклас, якби ви просто могли це зробити 123.Abs().

  • Передайте його навколо. Просте рішення, якщо методу потрібен якийсь інший об'єкт, - це просто передати його. Немає нічого поганого в тому, щоб передати деякі об’єкти навколо.

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


1
Ви можете тут підвести підсумки?
Ніколь

1
Зроблено, але я все ж закликаю вас прочитати весь розділ.
чудовий

4
інша альтернатива: введення залежності!
Бред Купіт

1
@BradCupit він також про це говорить за посиланням ... Треба сказати, я все ще намагаюся перетравити все це. Але це було найяскравішим читанням на одиночних кнопках, які я коли-небудь читав. До сих пір я був необхідні позитивні одинаків, так само як і глобальними змінними, і я сприяння в Toolbox . Тепер я просто більше не знаю. Пане муніфіцієнт, чи можете ви сказати мені, чи локатор сервісу - лише статичний інструментарій ? Чи не було б краще зробити його однотонним (таким чином, інструментальним)?
Крегокс

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

21

Сама проблема не є глобальною.

Дійсно вам потрібно лише хвилюватися global mutable state. Постійний стан не впливає на побічні афекти, і, отже, менше проблем.

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

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

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

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.

Що має сенс. Це також змушує мене думати, що я ніколи не зловживав Singleton, а тільки почав сумніватися у будь-якому використанні їх. Але не можу придумати жодного відвертого зловживання, яке я вчинив, згідно з цими пунктами. :)
Столи Бобі

2
(ви, мабуть, маєте на увазі не "за кажуть")
nohat

4
@nohat: Я є носієм "Англійської королеви" і, таким чином, відкидаю все, що виглядає по-французьки, якщо ми не зробимо це краще (наприклад, le weekendой, що це одна з наших). Спасибі :-)
Мартін Йорк

21
per se - латина.
Анон.

2
@Anon: Гаразд. Це не так уже й погано ;-)
Мартін Йорк

19

Тоді що? Оскільки цього ніхто не сказав: Панель інструментів . Тобто, якщо ви хочете глобальних змінних .

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

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

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

Але вгадайте, що? Це сингл!

А що таке одиночний?

Можливо, саме тут починається плутанина.

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

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

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

Не зрозумійте мене неправильно!

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

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

Інші альтернативи

  • Краща стаття , я тільки що прочитав про одиночках пропонує Service Locator в якості альтернативи. Для мене це, в основному, " Статичний інструментарій ", якщо ви хочете. Іншими словами, зробіть Локатор послуг однократним, і у вас з’явиться Панель інструментів. Це, безумовно, суперечить його початковій пропозиції уникати синглтона, звичайно, але це лише для того, щоб примусити проблему одинака полягає в тому, як він використовується, а не сам по собі шаблон.

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

Обидві варіанти вище - це хороші альтернативи. Але все залежить від вашого використання.

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

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

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

Практичні приклади

Я часто бачу 2 використовуваних як на користь, так і проти одиночних. Веб-кеш (мій випадок) та сервіс журналів .

Каротаж, деякі стверджують , є прекрасним прикладом Сінглтона, тому що, я цитую:

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

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

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


@gnat Спасибі! Я просто думав редагувати відповідь, щоб додати попередження щодо використання Singletons ... Ваша цитата ідеально підходить!
Крегокс

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

@gnat так, я знав, що це довгий бій. Сподіваюся, час покаже. ;-)
cregox

1
Я погоджуюся з вашим загальним напрямком тут, хоча це, можливо, трохи надто захоплено бібліотекою, яка, здається, не набагато більше, ніж надзвичайно голий кісток контейнера IoC (навіть більш базовий, ніж, наприклад, Funq ). Локатор послуг - це насправді анти-шаблон; це корисний інструмент, головним чином, у спадкових проектах, а також "DI DI", де було б занадто дорого переробляти все, щоб правильно використовувати контейнер IoC.
Aaronaught

1
@Cawas: Ах, вибачте - заплутався в java.awt.Toolkit. Моя думка однакова: Toolboxзвучить як мішок, що не пов'язаний між собою, і не є зв'язним класом з єдиною метою. Мені це не здається гарним дизайном. (Зауважте, що стаття, на яку ви посилаєтесь, - з 2001 року, перш ніж введення залежностей та контейнерів з ДІ стали звичним явищем.)
Джон Скіт

5

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

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

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

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

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


Чудово пояснено, це відповідь, яка найкраще пояснює питання про тестування одиночних
Хосе Томаш Точіно,

4

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

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

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

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

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


3

Використання одинарного шаблону, який представляє фактичні об'єкти, цілком прийнятно. Я пишу для iPhone, і в рамках Cocoa Touch є багато синглів. Сама програма представлена ​​однокласником класу UIApplication. Є лише один додаток, яким ви є, тож доречно представляти це синглетом.

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


2

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

API - приклад синглтона на рівні протоколу. Ви отримуєте доступ до Twitter, Google тощо через те, що фактично є одинаковими. То чому сингтонам стає погано в рамках програми?

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

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

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

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

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

Щось на зразок:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  

1

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

Що б ви зробили, це мати цей клас, але ВИДАЛИТИ ВСІ СТАТИЧНІ Члени Добре це не просто, але я рекомендую його. Дійсно, ви просто ініціалізуєте клас, як звичайний клас, і введіть вказівник. Не фріген скажіть ClassIWant.APtr (). LetMeChange.ANYTHINGATALL (). Andhave_no_structure ()

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


1

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

Графіка:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

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

Це з’єднує елементи, щоб ви могли додавати нові кеші, не входячи та змінюючи якийсь об'єкт Uber-Cache. YMMV. IANAL. ETC.


1

Трохи запізнюємось на вечірку, але все одно.

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

Врахуйте це:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

проти

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

1-й випадок призводить до високої зв'язку тощо; 2-й спосіб не має проблем @Aaronaught описує, наскільки я можу сказати. Все про те, як ви цим користуєтеся.


Не згоден. Хоча другий спосіб "кращий" (зменшення зчеплення), він все ще має проблеми, які вирішує DI. Ви не повинні покладатися на споживача свого класу, щоб забезпечити реалізацію ваших послуг - що краще робити в конструкторі, коли клас створений. Ваш інтерфейс повинен вимагати лише мінімуму з точки зору аргументів. Існує також гідний шанс, що для вашого класу потрібен єдиний екземпляр для роботи - знову ж таки, покладатися на споживача для виконання цього правила є ризиковим і непотрібним.
AlexFoxGill

Іноді Di - це надмірне значення для даного завдання. Метод у зразковому коді може бути конструктором, але не обов'язково - не дивлячись на конкретний приклад його аргумент суперечки. Крім того, DoSomething міг би взяти ISomething, а MySingleton міг реалізувати цей інтерфейс.
Євгеній

1

Дозвольте кожному екрану взяти менеджера у своєму конструкторі.

Запускаючи додаток, ви створюєте один екземпляр менеджера і передаєте його навколо.

Це називається Інверсія управління і дозволяє поміняти контролер під час зміни конфігурації та тестів. Крім того, ви можете запустити паралельно декілька примірників вашої програми або частини програми (добре для тестування!). Нарешті, ваш менеджер помре з власним об'єктом (клас запуску).

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

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