Чи існують життєздатні альтернативи шаблону GOF Singleton?


91

Давайте дивитися правді в очі. Шаблон Singleton - дуже суперечлива тема з ордами програмістів по обидва боки огорожі. Є ті, хто відчуває, що Сінглтон - це не що інше, як прославлена ​​глобальна змінна, і інші, хто клянеться шаблоном і невпинно його використовує. Однак я не хочу, щоб суперечка про Сінглтон лежала в основі мого запитання. Кожен може влаштувати перетягування каната, битися з ним і бачити, хто перемагає за все, що мене цікавить . Я намагаюся сказати, що я не вірю, що існує одна правильна відповідь, і я навмисно не намагаюся розпалити партизанські сварки. Мене просто цікавлять альтернативи-одиночки, коли я задаю питання:

Чи є їхні конкретні альтернативи шаблону GOF Singleton?

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

Яка у вас ще ідея?

РЕДАГУВАТИ: Я не дуже хочу, щоб це був черговий допис про "як правильно використовувати синглтон". Знову ж таки, я шукаю способів цього уникнути. Для розваги, добре? Думаю, я задаю чисто академічне запитання вашим найкращим голосом трейлера фільму: "Що ми можемо зробити в паралельному всесвіті, де немає синглтона?"


Що? Це ні добре, ні погано, але як я можу його замінити? Для всіх, хто каже, що це добре - не приймайте участі. Усі, хто каже, що це погано, доведіть це, показавши мені, як я можу жити без цього. Звучить мені аргументовано.
S.Lott,

@CodingWithoutComents: прочитав цілу публікацію. Ось так я зрозумів: "не відповідай, якщо з тобою самотні справи добре".
S.Lott,

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

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

9
Я використовую Singleton щодня, але чи це заважає мені думати, що, можливо, є кращий спосіб зробити щось? Шаблони дизайну існують лише 14 років. Чи сприймаю я їх як біблійну істину? Ми перестаємо намагатися мислити нестандартно? Хіба ми не намагаємось розвивати дисципліну КС?
CodingWithoutComments

Відповіді:


76

Алекс Міллер у " Шаблонах, які я ненавиджу " цитує наступне:

"Коли синглтон здається відповіддю, я вважаю, що часто розумніше:

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

2
Я знаю, що це вже близько 5 років, але чи можете ви продовжити це? Думаю, мене навчили, що правильний спосіб створити синглтон - це інтерфейс. Мені було б цікаво, якби те, що я робив, було «добре» і не було таким жахливим, як мені казали.
Pureferret,

97

Щоб зрозуміти правильний спосіб обходу синглтонів, потрібно зрозуміти, що не так із синглтонами (і загальним станом загалом):

Одиночні приховують залежності.

Чому це важливо?

Тому що якщо ви приховуєте залежності, ви, як правило, втрачаєте відстеження кількості зв’язку.

Ви можете заперечити це

void purchaseLaptop(String creditCardNumber, int price){
  CreditCardProcessor.getInstance().debit(creditCardNumber, amount);
  Cart.getInstance().addLaptop();
}

простіше ніж

void purchaseLaptop(CreditCardProcessor creditCardProcessor, Cart cart, 
                    String creditCardNumber, int price){
  creditCardProcessor.debit(creditCardNumber, amount);
  cart.addLaptop();
}

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

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


+1 Для обговорення кореня проблеми, а не просто того, як її обійти.
Філ

9
У повноцінному багаторівневому додатку другий метод часто створює величезну кількість непотрібного коду. Забруднюючи код середнього рівня логікою для перенесення на нижчі рівні, об’єкти, які в іншому випадку непотрібні на цьому рівні, хіба ми не створюємо залежностей, яких не потрібно? Скажімо, рівень 4 у навігаційному стеку ініціює фонову дію, як завантаження файлу. Тепер скажіть, що ви хочете попередити користувача, коли він завершиться, але на той момент користувач може знаходитись у зовсім іншій частині програми, яка лише спільно використовує рівень 1 із початковим поданням. Тонни непотрібного коду ...
Харі Карам Сінгх

1
@RasmusFaber Шаблон Singleton не приховує залежностей. Ви скаржитеся на приховування залежностей - це окрема проблема, що стосується шаблону. Це правда, що шаблон дозволяє легко зробити цю помилку, але дуже можливо виконати шаблон IoC та Singleton в гармонії. Наприклад, я можу створити сторонню бібліотеку, яка потребує спеціального управління життєвим циклом своїх екземплярів, і кожен, хто використовує мою бібліотеку, все одно повинен "передавати" екземпляр мого синглтона у свої класи, використовуючи класичну ін'єкцію залежності, а не "підключити небо" мого сингтона з кожної точки.
Martin Bliss

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

4
Також шаблони pub / sub або спостерігача можуть бути настільки ж поганими, що і "втрата сліду зв'язку", як це засвідчить будь-хто, кому доводилося проходити налагодження через структуру, яка в значній мірі покладається на них. Принаймні для одиночних, назва методу, що викликається, знаходиться там же, де і код, що викликає :) Особисто я думаю, що всі ці стратегії мають своє місце, і жодна не може бути реалізована наосліп. Недбалі кодери роблять спагетті з будь-якого зразка, і відповідальним кодерам не потрібно надмірно обтяжувати себе ідеологічними обмеженнями, щоб створити елегантний код.
Харі Карам Сінгх

14

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

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

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

class NeedyClass {

    private ExSingletonClass exSingleton;

    public NeedyClass(ExSingletonClass exSingleton){
        this.exSingleton = exSingleton;
    }

    // Here goes some code that uses the exSingleton object
}

А потім, завод.

class FactoryOfNeedy {

    private ExSingletonClass exSingleton;

    public FactoryOfNeedy() {
        this.exSingleton = new ExSingletonClass();
    }

    public NeedyClass buildNeedy() {
        return new NeedyClass(this.exSingleton);
    }
}

Оскільки ви створили екземпляр вашої фабрики лише один раз, буде одноразовий екземпляр exSingleton. Кожного разу, коли ви викликаєте buildNeedy, новий екземпляр NeedyClass буде в комплекті з exSingleton.

Сподіваюся, це допоможе. Вкажіть будь-які помилки.


5
щоб переконатися, що ваша Фабрика створюється лише тоді, коли вам потрібен приватний конструтор і визначте метод buildNeedy як статичний метод.
Жульєн Греньє

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

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

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

Отже, в основному перевага цього підходу полягає в тому, що вам доводиться перетасовувати лише один екземпляр об’єкта (завод), а не потенційно цілу купу об’єктів (для яких ви вирішили не використовувати Singletons)?
Харі Карам Сінгх

9

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


Є посилання на вищезазначені теми?
CodingWithoutComments

Ці фреймворки здаються специфічними для Java - будь-які інші мовні параметри? Або мовно-агностичний?
CodingWithoutComments

для .NET: Castle Windsor castleproject.org/container/index.html Microsoft Unity msdn.microsoft.com/en-us/library/cc468366.aspx Unity насправді є системою введення залежностей, але вона, ймовірно, спрацює для цієї теми.
Ерік ван Бракель,

1
чому за це не надто багато голосів? МОК - ідеальне рішення, щоб уникнути Сінглтона.
Сандіп Джиндал

@CodingWithoutComments Я знаю, що PHP має контейнер послуг Symfony. У рубінових чуваків є й інші способи введення залежностей. Чи є у вас конкретна мова, яка вас цікавить?
Bevelopper

9

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

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

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


Я збирався сказати те саме, але це не зовсім відповідає на його запитання :)
Суаті 02

2
Будь ласка, поговоріть у своїй відповіді, коли це доречно чи недоречно. Досить, будь ласка.
CodingWithoutComments

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

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

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

5

Monostate (описаний у роботі Agile Software Development Роберта К. Мартіна) є альтернативою одиночному. У цьому шаблоні дані класу всі статичні, але геттери / сеттери нестатичні.

Наприклад:

public class MonoStateExample
{
    private static int x;

    public int getX()
    {
        return x;
    }

    public void setX(int xVal)
    {
        x = xVal;
    }
}

public class MonoDriver
{
    public static void main(String args[])
    {
        MonoStateExample m1 = new MonoStateExample();
        m1.setX(10);

        MonoStateExample m2 = new MonoStateExample();
        if(m1.getX() == m2.getX())
        {
            //singleton behavior
        }
    }
}

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


Це заслуговує на більшу кількість голосів; Я прийшов сюди з Google, шукаючи оновлення MonoState!
mahemoff

@Mark Мені здається, що цю конструкцію дуже важко заблокувати з точки зору безпеки потоків. Чи створюю я блокування екземпляра для кожної статичної змінної в MonoStateExample, або створюю один замок, який використовують усі властивості? Обидва вони мають серйозні наслідки, які легко вирішити, виконавши за схемою Singleton.
Martin Bliss

Цей приклад є альтернативою шаблону Singleton, але не вирішує проблеми шаблону Singleton.
Сандіп Джиндал

4

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

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

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

*pseudocode* currentContainer.GetServiceByObjectType(singletonType)
//Under the covers the object might be a singleton, but this is hidden to the consumer.

замість єдиного глобального

*pseudocode* singletonType.Instance

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

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

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


Я не зовсім беру приклад з обслуговуваних контейнерів. Чи є у вас Інтернет-ресурси, на які ви могли б зробити посилання?
CodingWithoutComments

Погляньте на "Замковий проект - Віндзорський контейнер" castleproject.org/container/index.html . На жаль, дуже важко знайти абстрактні публікації на цю тему.
Pop Catalin

2

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

(хоча, я б стверджував, це спочатку неправильний спосіб використання Сінглтона)


1

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

Цей тип стилю дуже схожий на C і не дуже схожий на об’єкт - ви виявите, що всі об’єкти, що визначають вашу систему, матимуть посилання на той самий MumbleManager.


Не виступаючи за чи проти Сінглтона, я повинен сказати, що це рішення є анти-шаблоном. MumbleManager стає "класом Бога", який містить усі види різнорідних знань, порушуючи Єдину відповідальність. Це також може бути Singleton для всіх проблем програми.
Martin Bliss

0

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


1
Хіба фабрика часто не є одинаком? Саме так я зазвичай бачу, як реалізуються заводські зразки.
Томас Оуенс,

0

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

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

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


0

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


0

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

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

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


1
Я цілком розумію, про що ви говорите. І я цілком згоден. Однак ваша відповідь все одно не відповідає конкретно на моє запитання. Я не хотів, щоб це було черговим питанням "коли доречно використовувати синглтон?" Мене просто цікавили життєздатні альтернативи сінглону.
CodingWithoutComments

0

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


0

Не програмуючи в інтенсивно об’єктно-орієнтованому середовищі (наприклад, Java), я не повністю розбираюся в тонкощах дискусії. Але я застосував синглтон в PHP 4. Я зробив це як спосіб створення обробника бази даних "чорного ящика", який автоматично ініціалізується і не повинен передавати виклики функції вгору і вниз у неповній і дещо непрацюючій структурі.

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

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


-1

Що ви маєте на увазі, які мої прийоми, щоб цього уникнути?

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

Але таких немає. Мені не потрібно уникати одиночного візерунка. Це просто не виникає.

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