Альтернативи синглетону

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

Одна ситуація, в якій я використовую синглтон, це коли мені потрібна фабрика (скажемо, об'єкт f типу F) для створення об'єктів певного класу А. Фабрика створюється один раз за допомогою деяких параметрів конфігурації, а потім використовується при кожному об'єкті типу А. Так що кожна частина Росії код, який хоче створити екземпляр A, отримує синглетон f і створює новий екземпляр, напр.

F& f                   = F::instance();
boost::shared_ptr a = f.createA();

Отже, загальний сценарій - це

  1. Мені потрібен лише один екземпляр класу або з метою оптимізації (мені не потрібні кілька об'єктів заводу), або для спільного використання (наприклад, завод знає, скільки екземплярів A все ще може бути створено)
  2. Мені потрібен спосіб отримати доступ до цього екземпляра f of F у різних місцях коду.

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

Ідеї, які я мав, були (1), щоб отримати об'єкт заводу з реєстру або (2) створити фабрику в певний момент під час запуску програми, а потім передавати завод навколо як параметр.

У рішенні (1), сам реєстр є одиночним, тому я тільки що змінився Проблема невикористання singleton від заводу до реєстру.

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

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

EDIT

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

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

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

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

27
@Falcon: Залежність від ін'єкцій ... що? Д. нічого не робить для вирішення цієї проблеми, хоча це часто дає вам зручний спосіб його приховати.
додано Автор fluffels, джерело
related (можливо, дублікат): can-have-one-i "> Які альтернативи для одного елемента існують для класу, який може мати лише один екземпляр?
додано Автор gnat, джерело
Ін'єкція залежностей ...
додано Автор Falcon, джерело
@Falcon Ви маєте на увазі IoC контейнер? Це дозволить вам вирішувати залежності в одному рядку. Наприклад, Dependency.Resolve (); або Dependency.Resolve () .DoSomething ();
додано Автор CodeART, джерело
Це досить старе питання, і вже відповіли. Я хоч і хотів би зв'язати це з назвою: = "чи є якісь альтернативи для шаблону gof singleton"> stackoverflow.com/questions/162042/…
додано Автор TheSilverBullet, джерело

9 Відповіді

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

Замість того, щоб одностороння, я вважаю за краще використовувати один з:

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

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

17
додано
Це дуже гарна відповідь, але я не запитував, чому я повинен/не повинен використовувати синглетонний зразок. Я знаю проблеми, які можуть виникнути в глобальному стані. У будь-якому випадку, питання питання полягало в тому, як мати унікальні об'єкти, а в той же час вільну реалізацію в одиночній моделі.
додано Автор Giorgio, джерело

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

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

7
додано

Є кілька варіантів:

Залежність ін'єкції

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

Реєстр послуг

Every object is passed a Реєстр послуг object. It provides methods to request various different objects which provide different services. The Android framework uses this pattern

Кореневий менеджер

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

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()
3
додано
@Giorgio, це може бути. Він також може бути розміщений на стеку. Але вона не зберігається в глобальній змінної/статичній змінної/еквіваленті.
додано Автор BCS, джерело
@Giorgio, кореневий об'єкт не є одиночним, оскільки він не зберігається в глобальному стані. Є просто багато покажчиків на нього.
додано Автор BCS, джерело
@KonradRudolph, тоді ви дійсно використовуєте якийсь гібридний підхід Singleton/DI. Моя пуристська сторона вважає, що ви повинні йти на чистий підхід. Проте більшість проблем, пов'язаних з однозначною проблемою, не існує.
додано Автор BCS, джерело
@KonradRudolph, чи інші методи реалізовані за допомогою Singletons або DI? Так і Ні. Зрештою, ви або маєте передати об'єкти або зберегти об'єкти в глобальному стані. Але існують тонкі відмінності в тому, як ви це робите. Вищезазначені три версії уникають використання глобального стану; але спосіб, яким кінцевий код отримує посилання, відрізняється.
додано Автор BCS, джерело
@KonradRudolph, з DI залежностей явні, і об'єкт не робить припущень про те, як даний ресурс поставляється. У синглетонах залежність є неявною, і кожне використання робить припущення, що буде тільки один такий об'єкт.
додано Автор BCS, джерело
Використання синглетонів не передбачає жодного об'єкта, якщо синглтон реалізує інтерфейс (або навіть якщо його немає), і ви передаєте примірник синглетону методам, замість того, щоб запитувати Singleton :: instance скрізь у код клієнта.
додано Автор Mark Ransom, джерело
Окрім насмішок, яка перевага має DI над одноразовим? Це питання, яке довго тримало мене. Що стосується менеджера реєстру та кореневих систем, то чи не вони реалізовані як однотонні, так і через DI? (Фактично, контейнер IoC є реєстром, чи не так?)
додано Автор Mark Ransom, джерело
Таким чином, з кореневим менеджером ви зменшите кількість одиночних одиниць на 1 (об'єкт кореневого менеджера), але не виключіть синглони повністю.
додано Автор Giorgio, джерело
Ви маєте на увазі, що кореневий об'єкт є об'єктом стека?
додано Автор Giorgio, джерело
Так що, можливо, це рішення я мав на увазі (що я намалював у останньому пункті мого питання).
додано Автор Giorgio, джерело

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

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

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

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

Edit: Ultimately, everything depends on the main method anyway. But you're right: the only way to avoid the use of global/static completely is by having everything set up from the main method. Note that DI is most popular in server environments where the main method is opaque and sets up the server and application code consists basically of callbacks which are instantiated and called by the server code. But most DI frameworks also allow direct access to their central registry, e.g. Spring's ApplicationContext, for "special cases".

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

3
додано
@Giorgio: ніщо в оригінальному запиті не вказувало на те, що ви вже знайомі з ін'єкціями залежностей. І див. Оновлену відповідь.
додано Автор kyck-ling, джерело
@Giorgio: Легше зрозуміти з конкретним прикладом: скажімо, у вас є додаток Java EE. Логіка вашого інтерфейсу полягає в способі дії керованого компонента JSF. Коли користувач натискає кнопку, контейнер JSF створює екземпляр керованого Bean і викликає метод дії. Але до цього компонент DI розглядає поля класу Managed Bean, а ті, які мають певну анотацію, заповнюються посиланням на об'єкт Service відповідно до конфігурації DI, і ці об'єкти служби мають те ж саме, що відбувається з ними . Метод дії може викликати служби.
додано Автор kyck-ling, джерело
@Giorgio: DI завжди включає в себе такий реєстр, але головне, що робить його краще, це те, що замість того, щоб запитувати реєстр, де б вам не потрібен об'єкт, у вас є центральні об'єкти, які транзитивно заповнюються, як я писав вище.
додано Автор kyck-ling, джерело
Деякі люди (у тому числі і ви) не читають питання ретельно і не розуміють, що насправді просять.
додано Автор Giorgio, джерело
Вибачте, якщо мій коментар звучав трохи грубо, але початок вашої відповіді теж дуже прямий (навіть якщо ви, мабуть, маєте рацію, що на моє запитання не вистачає інформації). Я намагався пояснити своє питання краще, щоб уникнути непорозумінь.
додано Автор Giorgio, джерело
Ви маєте на увазі, що інжекція залежностей основної ідеї в основному є першим підходом, наведеним вище (наприклад, за допомогою реєстру), нарешті основна ідея?
додано Автор Giorgio, джерело

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

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

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

1
додано

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

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

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

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

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

1
додано
Особисто я бачу це як просто іншу реалізацію синглетону. У цьому випадку одиночний екземпляр є самим класом. Зокрема: він має всі ті ж недоліки, що і "реальний" синглтон.
додано Автор MikeJ-UK, джерело
@Randall Cook: Я думаю, що ваша відповідь захоплює мою точку зору. Дуже часто я читав, що синглтон дуже поганий і його слід уникати будь-якою ціною. Я згоден з цим, але, з іншого боку, я думаю, що технічно неможливо завжди уникати цього. Коли вам знадобиться «одне з чогось», ви повинні створити його десь і отримати доступ до нього.
додано Автор Giorgio, джерело

Інкапсуляція окремих сторінок

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

До прикладу (C ++, як псевдокод):

// network info
class Session {
 //...
};

// current user connection info
class CurrentUser {
 //...
};

// configuration upon user
class UserConfiguration {
 //...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
 //...
};

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

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

Після прикладу (C ++, як псевдокод):

// network info
class NetworkInfoClass {
 //...
};

// current user connection info
class CurrentUserClass {
 //...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
 //...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
 //...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

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

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

Підбадьорює

0
додано

Як щодо використання контейнера IoC? З деякою думкою ви можете закінчити з чимось на лініях:

Dependency.Resolve(); 

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

Метод статичних синглетонних пустот можна замінити на:

Dependency.Resolve().DoSomething();

Статичний метод одинарного геттера може бути замінений на:

var result = Dependency.Resolve().GetFoo();

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

0
додано

Ваші оригінальні вимоги:

So the general my scenario is that

  1. I need only one instance of a class either for optimization reasons (I do not need >multiple factory objects) or for sharing common state (e.g. the factory knows how many >instances of A it can still create)
  2. I need a way to have access to this instance f of F in different places of the code.

Не вирівнюйте з визначенням одиночного (і те, що ви хочете згадати пізніше). З GoF (моя версія 1995) стор. 127:

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

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

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

Щоб уникнути глобальної доступності, підходи до спільної роботи «зробіть примірник і передайте їх разом» або «мають власника двох об'єктів склеювати їх разом» добре працюють.

0
додано