Альтернативи однотонному малюнку


27

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

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

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

Тож загальний мій сценарій такий

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

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

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

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

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

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

EDIT

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

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

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

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


8
Ін'єкція залежностей ...
Сокіл

1
@Falcon: Впорскування залежності ... що? DI не робить нічого для вирішення цієї проблеми, хоча часто дає вам корисний спосіб її приховати.
пдр

@Falcon Ви маєте на увазі контейнер IoC? Це дозволить вирішити залежності в одному рядку. Наприклад залежність. Розв’язати <IFoo> (); або Dependency.Resolve <IFoo> () .DoSomething ();
CodeART

Це досить старе питання, і вже відповів. Я до сих пір , хоча це було б добре , щоб зв'язати цей: stackoverflow.com/questions/162042 / ...
TheSilverBullet

Відповіді:


7

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

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


17

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

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

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

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


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

3

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

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

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

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

Редагувати: Зрештою, все одно залежить від основного методу. Але ви маєте рацію: єдиний спосіб уникнути використання глобального / статичного повністю - це все налаштовано з основного методу. Зауважте, що DI є найпопулярнішим у серверних середовищах, де основний метод непрозорий і встановлює сервер і код програми, в основному складається з зворотних викликів, які ініціюються та викликаються кодом сервера. Але більшість фреймворків DI також дозволяють отримати прямий доступ до їх центрального реєстру, наприклад Spring's ApplicationContext, для "особливих випадків".

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


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

@Giorgio: DI завжди включає такий реєстр, але головне, що робить його краще, це те, що замість того, щоб запитувати реєстр там, де вам потрібен об'єкт, у вас є центральні об'єкти, які є транзитивно заповненими, як я писав вище.
Майкл Боргвардт

@Giorgio: Простіше зрозуміти на конкретному прикладі: скажімо, у вас є програма Java EE. Ваша передня логіка полягає в методі дії керованого квадратика JSF. Коли користувач натискає кнопку, контейнер JSF створює екземпляр керованого квасолі та викликає метод дії. Але перед цим компонент DI розглядає поля класу керованих Bean, а ті, що мають певну анотацію, заповнюються посиланням на об’єкт служби, що відповідає конфігурації DI, і ці об’єкти служби мають те саме, що відбувається з ними . Потім спосіб дії може викликати служби.
Майкл Боргвардт

Деякі люди (включаючи вас) не читають запитання уважно і неправильно розуміють, що насправді задають.
Джорджіо

1
@Giorgio: в первинному запитанні нічого не вказувало на те, що ви вже знайомі з ін'єкцією залежностей. І дивіться оновлену відповідь.
Майкл Боргвардт

3

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

Ін'єкційна залежність

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

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

Кожен об’єкт передається об'єктом реєстру послуг. Він надає методи запиту різних різних об'єктів, які надають різні послуги. Рамка Android використовує цю схему

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

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

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

Окрім глузування, яку перевагу має DI у використанні одинарного? Це питання, яке мене дуже довго турбує. Що стосується реєстру та кореневого менеджера, чи не реалізуються вони як одиночні або через DI? (Насправді контейнер IoC - це реєстр, правда?)
Конрад Рудольф

1
@KonradRudolph, з DI залежності явні, і об'єкт не робить припущень щодо того, як подається даний ресурс. Що стосується одинаків, залежності є неявними, і кожне їх використання робить припущення, що коли-небудь буде такий єдиний об'єкт.
Вінстон Еверт

@KonradRudolph, чи інші методи реалізовані за допомогою Singletons або DI? Так і ні. Зрештою, вам або доведеться передавати об'єкти або зберігати об’єкти у глобальному стані. Але є тонкі відмінності в тому, як ви це робите. Згадані три версії уникають використання глобального стану; але спосіб, яким кінцевий код отримує посилання, відрізняється.
Вінстон Еверт

Використання одиночних символів не передбачає жодного об'єкта, якщо сингтон реалізує інтерфейс (або навіть якщо ні), і ви передаєте одиночний екземпляр методам, а не запитуйте Singleton::instanceвсюди в коді клієнта.
Конрад Рудольф

@KonradRudolph, тоді ти справді використовуєш гібридний підхід Singleton / DI. Моя пуристська сторона вважає, що ви повинні піти на чисто підхід до DI. Однак більшість проблем із синглтон насправді там не стосуються.
Вінстон Еверт

1

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

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

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

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

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

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


4
Особисто я вважаю це лише різною реалізацією одинарного шаблону. У цьому випадку екземпляр singleton є самим класом. Конкретно: він має всі ті ж недоліки, що і "справжній" синглтон.
Йоахім Зауер

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

1

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

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

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


0

Сингл-інкапсуляція

Сценарій випадку. Ваша програма орієнтована на об'єкт і вимагає 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

Ваші початкові вимоги:

Тож загальний мій сценарій такий

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

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

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

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

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

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


0

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

Dependency.Resolve<IFoo>(); 

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

Статичний метод одиночної недійсності можна замінити на:

Dependency.Resolve<IFoo>().DoSomething();

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

var result = Dependency.Resolve<IFoo>().GetFoo();

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

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