Альтернативы единому образцу

27

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

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

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

Так что общий мой сценарий таков, что

  1. Мне нужен только один экземпляр класса либо по причинам оптимизации (мне не нужны несколько объектов фабрики), либо для совместного использования общего состояния (например, фабрика знает, сколько экземпляров A она все еще может создать)
  2. Мне нужен способ получить доступ к этому экземпляру f из F в разных местах кода.

Я не заинтересован в обсуждении, является ли этот шаблон хорошим или плохим, но если предположить, что я хочу избежать использования синглтона, какой другой шаблон я могу использовать?

У меня были идеи (1) получить фабричный объект из реестра или (2) создать фабрику в какой-то момент во время запуска программы, а затем передать фабрику как параметр.

В решении (1) сам реестр является одноэлементным, поэтому я только что перенес проблему неиспользования одного экземпляра с завода на реестр.

В случае (2) мне нужен некоторый исходный источник (объект), из которого происходит фабричный объект, поэтому я боюсь, что я снова вернусь к другому синглтону (объекту, который предоставляет мой экземпляр фабрики). Следуя этой цепочке синглетонов, я могу свести проблему к одному синглтону (целому приложению), с помощью которого все другие синглтоны управляются прямо или косвенно.

Будет ли этот последний вариант (с использованием одного исходного синглтона, который создает все другие уникальные объекты и внедряет все другие синглтоны в нужных местах) приемлемым решением? Является ли это решением, которое неявно предлагается, когда кто-то советует не использовать синглтоны, или каковы другие решения, например, в примере, показанном выше?

РЕДАКТИРОВАТЬ

Поскольку я думаю, что суть моего вопроса была неправильно понята некоторыми, вот еще немного информации. Как объяснено, например, здесь , слово singleton может указывать (а) на класс с одним экземпляром объекта и (б) шаблон проектирования, используемый для создания и доступа к такому объекту.

Чтобы прояснить ситуацию, давайте используем термин уникальный объект для (а) и шаблон синглтона для (б). Итак, я знаю, что такое одноэлементный шаблон и внедрение зависимостей (кстати, в последнее время я интенсивно использую DI для удаления экземпляров одноэлементного шаблона из некоторого кода, над которым я работаю).

Моя точка зрения заключается в том, что, если весь объектный граф не будет создан из одного объекта, находящегося в стеке основного метода, всегда будет необходимость доступа к некоторым уникальным объектам через шаблон синглтона.

Мой вопрос заключается в том, является ли единственное решение, не связанное с одноэлементным шаблоном, от того, зависит ли полное создание графов объектов и связывание от основного метода (например, с помощью некоторой мощной структуры DI, которая не использует сам шаблон) .

Джорджио
источник
8
Инъекция зависимости ...
Сокол
1
@Falcon: инъекция зависимостей ... что? DI ничего не делает для решения этой проблемы, хотя часто дает вам удобный способ ее скрыть.
ПДР
@ Falcon ты имеешь в виду контейнер IoC? Это позволит вам разрешить зависимости в одной строке. Например, Dependency.Resolve <IFoo> (); или Dependency.Resolve <IFoo> (). DoSomething ();
CodeART
Это довольно старый вопрос, и на него уже дан ответ. Я все еще, хотя было бы хорошо связать это: stackoverflow.com/questions/162042/…
TheSilverBullet

Ответы:

7

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

Вы не можете обойти тот факт, что что- то должно создать вашу фабрику. Если это что-то происходит с приложением, пусть будет так. Важным моментом является то, что вашей фабрике должно быть все равно, какой объект ее создал, а объекты, получающие фабрику, не должны зависеть от того, откуда она родом. Не заставляйте ваши объекты получать указатель на синглтон приложения и запрашивать его для фабрики; пусть ваше приложение создаст фабрику и передаст ее тем объектам, которые в ней нуждаются.

Калеб
источник
17

Цель синглтона состоит в том, чтобы обеспечить существование только одного экземпляра в определенной области. Это означает, что синглтон полезен, если у вас есть веские причины для принудительного поведения синглтона; на практике это случается редко, а многопроцессорность и многопоточность, безусловно, стирают значение слова «уникальный» - это один экземпляр на машину, на процесс, на поток, на запрос? А ваша одиночная реализация заботится об условиях гонки?

Вместо синглтона я предпочитаю использовать одно из:

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

Причина в том, что синглтон является скрытым глобальным состоянием, а это означает, что он обеспечивает высокую степень связи во всем приложении - любая часть вашего кода может захватывать экземпляр синглтона из любого места с минимальными усилиями. Если вы используете локальные объекты или пропускаете экземпляры, у вас гораздо больше контроля, и вы можете держать свои области небольшого размера и узкие зависимости.

tdammers
источник
3
Это очень хороший ответ, но я не спрашивал, почему я не должен / не должен использовать шаблон синглтона. Я знаю проблемы, которые могут возникнуть с глобальным состоянием. В любом случае, тема вопроса заключалась в том, как иметь уникальные объекты и в то же время реализовать их без использования шаблона.
Джорджио
3

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

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

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

Преимущество состоит в том, что существующие структуры DI являются гибкими, мощными и хорошо протестированными. Они могут сделать гораздо больше, чем просто управлять синглетонами. Однако, чтобы в полной мере использовать их возможности, они лучше всего работают, если вы структурируете свое приложение определенным образом, что не всегда возможно: в идеале, существуют центральные объекты, которые получены через инфраструктуру DI и все их зависимости транзитивно заполняются и затем выполнен.

Изменить: В конечном итоге все зависит от основного метода в любом случае. Но вы правы: единственный способ полностью избежать использования global / static - это настроить все из основного метода. Обратите внимание, что DI наиболее популярен в серверных средах, где основной метод непрозрачен и устанавливает сервер, а код приложения состоит в основном из обратных вызовов, которые создаются и вызываются кодом сервера. Но большинство DI-структур также предоставляют прямой доступ к своему центральному реестру, например SpringCon ApplicationContext, для «особых случаев».

Так что в основном лучшее, что люди придумали, - это умная комбинация двух альтернатив, которые вы упомянули.

Майкл Боргвардт
источник
Вы имеете в виду, что внедрение основной идеи - это в основном первый подход, который я набросал выше (например, использование реестра), наконец, основная идея?
Джорджио
@ Джорджио: DI всегда включает в себя такой реестр, но главное, что делает его лучше, состоит в том, что вместо того, чтобы запрашивать реестр там, где вам нужен объект, у вас есть центральные объекты, которые заполняются транзитивно, как я писал выше.
Майкл Боргвардт
@ Джорджио: Это проще понять на конкретном примере: допустим, у вас есть приложение Java EE. Ваша логика внешнего интерфейса находится в методе действия управляемого бина JSF. Когда пользователь нажимает кнопку, контейнер JSF создает экземпляр управляемого объекта и вызывает метод действия. Но перед этим компонент DI просматривает поля класса Managed Bean, а те, которые имеют определенную аннотацию, заполняются ссылкой на объект Service в соответствии с конфигурацией DI, и с этими объектами Service происходит то же самое , Затем метод действия может вызвать службы.
Майкл Боргвардт
Некоторые люди (включая вас) не читают вопросы внимательно и неправильно понимают, что на самом деле задают.
Джорджио
1
@ Джорджио: ничто в исходном вопросе не указывало на то, что вы уже знакомы с внедрением зависимостей. И посмотрите обновленный ответ.
Майкл Боргвардт
3

Есть несколько альтернатив:

Внедрение зависимости

Каждый объект имеет свои зависимости переданы ему, когда он был создан. Обычно за создание и связывание объектов отвечают либо каркас, либо небольшое количество фабричных классов.

Сервисный реестр

Каждому объекту передается объект реестра службы. Он предоставляет методы для запроса различных объектов, которые предоставляют разные услуги. Платформа Android использует этот шаблон

Root Manager

Существует один объект, который является корнем графа объекта. По ссылкам из этого объекта любой другой объект может быть в конечном итоге найден. Код имеет тенденцию выглядеть так:

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
Лично я рассматриваю это как просто другую реализацию шаблона синглтона. В этом случае единичным экземпляром является сам класс. Конкретно: у него есть все те же недостатки, что и у «настоящего» синглтона.
Иоахим Зауэр
@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();
};

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

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

Приветствия.

umlcat
источник
0

Ваши оригинальные требования:

Так что общий мой сценарий таков, что

  1. Мне нужен только один экземпляр класса либо по причинам оптимизации (мне не нужно> несколько объектов фабрики), либо для совместного использования общего состояния (например, фабрика знает, сколько> экземпляров A она все еще может создать)
  2. Мне нужен способ получить доступ к этому экземпляру f из F в разных местах кода.

Не совпадайте с определением синглтона (и того, к чему вы обращаетесь позже). С GoF (моя версия 1995) стр. 127:

Убедитесь, что у класса есть только один экземпляр, и предоставьте глобальную точку доступа к нему.

Если вам нужен только один экземпляр, это не мешает вам делать больше.

Если вам нужен один глобально доступный экземпляр, создайте один глобально доступный экземпляр . Нет необходимости иметь шаблон имени для всего, что вы делаете. И да, отдельные глобально доступные экземпляры обычно плохие. Если дать им имя, это не сделает их менее плохими .

Чтобы избежать глобальной доступности, общий подход «создай экземпляр и передай его» или «владелец двух объектов склеит их вместе» работает хорошо.

Telastyn
источник
0

Как насчет использования контейнера IoC? С некоторыми мыслями вы можете получить что-то вроде:

Dependency.Resolve<IFoo>(); 

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

Статический метод синглтоновых пустот можно заменить на:

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

Метод статического одноэлементного геттера можно заменить на:

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

Пример относится к .NET, но я уверен, что очень похожее может быть достигнуто на других языках.

CodeART
источник