Значит синглтоны плохие, тогда что?


554

В последнее время было много дискуссий о проблемах с использованием (и чрезмерным использованием) Singletons. Я был одним из тех людей в начале моей карьеры тоже. Теперь я вижу, в чем проблема, и все же есть много случаев, когда я не вижу хорошей альтернативы, и не многие из дискуссий по борьбе с синглтоном действительно дают такую ​​возможность.

Вот реальный пример крупного недавнего проекта, в котором я принимал участие:

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

Есть ли какой-то другой подход, который мог бы быть уместным здесь, чем в основном наличие такого типа объекта кэша глобального менеджера данных? Этот объект официально не обязательно должен быть «синглтоном», но концептуально имеет смысл быть им. Какая хорошая чистая альтернатива здесь?


10
Какую проблему должно решить использование синглтона? Как лучше решать эту проблему, чем альтернативы (например, статический класс)?
Анон.

14
@Anon: Как использование статического класса делает ситуацию лучше. Есть еще жесткая связь?
Мартин Йорк

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

9
@ Анон: Не правда. Статические классы не дают (почти) никакого контроля над созданием экземпляров и делают многопоточность еще более сложной, чем это делают синглтоны (так как вам нужно сериализовать доступ к каждому отдельному методу, а не только к экземпляру). Singletons также может по крайней мере реализовать интерфейс, который статические классы не могут. Статические классы, безусловно, имеют свои преимущества, но в этом случае синглтон определенно является меньшим из двух значительных зол. Статический класс, который реализует любое изменяемое состояние, напоминает большой мигающий неон "ПРЕДУПРЕЖДЕНИЕ: ПЛОХОЙ ДИЗАЙН ВПЕРЕД!" подписать.
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
Почему Cache не может быть синглтоном? Разве это не синглтон, если вы передаете его и используете инъекцию зависимостей? Синглтон просто ограничивает себя одним экземпляром, а не о том, как к нему обращаются, верно? Посмотрите мое мнение
Эрик Энгхейм

29
@AdamSmith: Вы на самом деле читали любой из этих ответов? Ваш вопрос дан ответ в первых двух параграфах. Шаблон Singleton! == Одиночный экземпляр.
Aaronaught

5
@Cawas and Adam Smith - Читая ваши ссылки, я чувствую, что вы на самом деле не читали этот ответ - все уже там.
Уилберт

19
@Cawas Я чувствую, что суть этого ответа - различие между единичным экземпляром и одиночным. Синглтон - это плохо, а один экземпляр - нет. Внедрение зависимостей - это хороший общий способ использования единичных экземпляров без использования синглетонов.
Уилберт

48

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

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

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

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

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

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

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

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

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

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


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

1
<cont> Это по сути прозрачно. В том смысле, что существует обратный вызов с сервера, если определенные требуемые данные еще не кэшированы. Но реализация (логически и физически) этого менеджера кеша - это Singleton.
Бобби Столы

6
Я с qstarin здесь: Объекты, обращающиеся к данным, не должны знать (или должны знать), что данные кэшируются (это деталь реализации). Пользователи данных просто запрашивают данные (или запрашивают интерфейс для извлечения данных).
Мартин Йорк

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

2
@ Бобби Таблицы: тогда ваша ситуация не так страшна, как казалось. Этот Singleton (при условии, что вы имеете в виду статический класс, а не просто объект с экземпляром, который живет столько же времени, сколько приложение) по-прежнему проблематичен. Он скрывает тот факт, что ваш объект предоставления данных зависит от поставщика кэша. Лучше, если это будет явным и внешним. Разделите их. Это важно для контролируемости , что вы можете легко заменить компоненты и поставщик кэша является главным примером такого компонента (как часто является поставщик кэша при поддержке ASP.Net).
Квентин Старин

45

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

ТЛ; др:

Шаблон Gang of Four Singleton делает две вещи: предоставляет вам удобный доступ к объекту из любого места и гарантирует, что может быть создан только один его экземпляр. В 99% случаев все, что вас волнует, - это первая половина этого, а перемещение по второй половине, чтобы получить его, добавляет ненужные ограничения.

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

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

  • Отвергни это полностью. Я видел много одноэлементных классов, которые не имеют никакого состояния и являются просто пакетами вспомогательных функций. Этим не нужен экземпляр вообще. Просто сделайте их статическими функциями или переместите их в один из классов, которые функция принимает в качестве аргумента. Тебе не понадобился бы специальный Mathкласс, если бы ты мог просто сделать 123.Abs().

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

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


1
Вы можете подвести итог здесь?
Николь

1
Готово, но я все же призываю вас прочитать всю главу.
Великолепно

4
другая альтернатива: инъекция зависимости!
Брэд Купит

1
@BradCupit, он тоже говорит об этом по ссылке ... Должен сказать, что я все еще пытаюсь переварить все это. Но это было самое разъясняющее чтение на синглетах, которые я когда-либо читал. До сих пор я был необходимы положительные одиночек, так же как и глобальными переменными, и я содействие в Toolbox . Теперь я просто не знаю больше. Господин munificient, можете ли вы сказать мне, является ли локатор сервисов статическим набором инструментов ? Разве не было бы лучше сделать его синглтоном (таким образом, набором инструментов)?
Cregox

1
«Панель инструментов» для меня выглядит очень похоже на «Сервисный локатор». Я думаю, что для большинства программ это не так важно, используете ли вы для него статическое изображение или делаете его единичным. Я склонен склоняться к статике, потому что зачем иметь дело с ленивой инициализацией и выделением кучи, если это не нужно?
Великолепно

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.

Это имеет смысл. Это также заставляет меня думать, что я никогда не злоупотреблял Синглетонами, а просто начал сомневаться в ЛЮБОМ их использовании. Но не могу думать ни о каком прямом оскорблении, которое я сделал, согласно этим пунктам. :)
Бобби Столы

2
(Вы, вероятно, имеете в виду, по сути, не "per say")
nohat

4
@nohat: Я являюсь носителем языка "Queens English" и поэтому отвергаю все, что выглядит по-французски, если только мы не сделаем это лучше (как, к le weekendсожалению, это один из наших). Спасибо :-)
Мартин Йорк

21
По сути, это латинский.
Анон.

2
@ Анон: ОК. Это не так уж и плохо ;-)
Мартин Йорк

19

Тогда что? Так как никто не сказал это: Toolbox . Это если вы хотите глобальные переменные .

Злоупотребления синглтоном можно избежать, если взглянуть на проблему с другой стороны. Предположим, что приложению требуется только один экземпляр класса, и приложение настраивает этот класс при запуске: почему сам класс должен быть ответственным за то, чтобы быть одиночным? Кажется вполне логичным, что приложение берет на себя эту ответственность, поскольку приложение требует такого поведения. Приложение, а не компонент, должно быть единственным. Затем приложение делает экземпляр компонента доступным для использования любым кодом приложения. Когда приложение использует несколько таких компонентов, оно может объединить их в так называемый набор инструментов.

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

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 в качестве альтернативы. Для меня это, по сути, « Static Toolbox », если хотите. Другими словами, сделайте Service Locator синглтоном, и у вас будет набор инструментов. Конечно, это идет вразрез с его первоначальным предложением избегать синглтона, но это только для того, чтобы навязать проблему синглтона в том, как он используется, а не в самом паттерне.

  • Другие предлагают Factory Pattern в качестве альтернативы. Это была первая альтернатива, которую я услышал от коллеги, и мы быстро исключили ее для использования в качестве глобальной переменной . Это конечно имеет свое использование, но так же как и синглтоны.

Обе альтернативы выше являются хорошими альтернативами. Но все зависит от вашего использования.

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

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

Неспособности (абстрактно или подкласс) действительно есть, но что с того? Это не для этого. Насколько я могу судить, нет возможности для интерфейса . Высокое сцепление также может быть там, но это только потому , что , как это обычно используется. Это не обязательно . Фактически, сама связь не имеет ничего общего с одноэлементной структурой. Это уточняется, но это также устраняет сложность тестирования. Что касается сложности распараллеливания, это зависит от языка и платформы, поэтому, опять же, не проблема для шаблона.

Практические примеры

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

Регистрация, как утверждают некоторые , является идеальным примером синглтона, потому что, и я цитирую:

  • Запрашивающим нужен хорошо известный объект для отправки запросов на регистрацию. Это означает глобальную точку доступа.
  • Так как служба ведения журнала является единственным источником событий, на который могут регистрироваться несколько слушателей, необходим только один экземпляр.
  • Хотя разные приложения могут регистрировать на разных устройствах вывода, способ, которым они регистрируют своих слушателей, всегда одинаков. Все настройки выполняются через слушателей. Клиенты могут запросить регистрацию, не зная, как и где будет записан текст. Следовательно, каждое приложение будет использовать сервис регистрации точно так же.
  • Любое приложение должно иметь возможность использовать только один экземпляр службы регистрации.
  • Любой объект может быть инициатором регистрации, включая повторно используемые компоненты, так что они не должны быть связаны с каким-либо конкретным приложением.

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

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


@gnat Спасибо! Я просто думал, редактируя ответ, чтобы добавить предупреждение об использовании Singletons ... Ваша цитата идеально подходит!
Cregox

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

@gnat да, я знал, что это долгая битва. Надеюсь, время покажет. ;-)
cregox

1
Я согласен с вашим общим указанием здесь, хотя он, возможно, немного излишне увлечен библиотекой, которая, кажется, не намного больше, чем контейнер IoC с чрезвычайно простыми возможностями (даже более простой, чем, например, Funq ). Сервисный локатор на самом деле является анти-паттерном; это полезный инструмент в основном в устаревших проектах, а также в «DI для бедняков», где было бы слишком дорого подвергать рефакторингу все для правильного использования контейнера IoC.
Aaronaught

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

5

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

Каждый компонент, имеющий зависимость от этого «менеджера», делает это путем запроса своего экземпляра-одиночки. И если вы хотите написать модульный тест для такого компонента, вам нужно внедрить данные в этот одноэлементный экземпляр, что может быть непросто.

Если, с другой стороны, ваш «менеджер» внедряется в зависимые компоненты через параметр конструктора, и компонент не знает конкретный тип менеджера, только интерфейс или абстрактный базовый класс, который реализует менеджер, тогда модуль test может предоставить альтернативные реализации менеджера при тестировании зависимостей.

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

Но если вас не интересуют юнит-тесты, тогда шаблон одноэлементного дизайна вполне подойдет. (но я бы не стал это делать в любом случае)


Хорошо объясненный, это ответ, который лучше всего объясняет проблему тестирования Singletons
José Tomás Tocino

4

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

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

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

Когда синглтон на самом деле не является синглтоном (это то, когда большинство программистов расстраиваются), это ленивый глобализированный экземпляр, нет возможности внедрить правильный экземпляр.

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


3

Использование одноэлементного шаблона, который представляет реальные объекты, вполне приемлемо. Я пишу для iPhone, и в рамках Cocoa Touch есть много синглетонов. Само приложение представлено синглтоном класса UIApplication. Есть только одно приложение, которым вы являетесь, поэтому уместно представить это с помощью синглтона.

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


2

Синглтоны - это просто проекция ориентированной на обслуживание архитектуры в программу.

API является примером синглтона на уровне протокола. Вы получаете доступ к Твиттеру, 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. И Т.П.


1

Немного опоздал на вечеринку, но все равно.

Singleton - это инструмент в наборе инструментов, как и все остальное. Надеюсь, у вас есть больше в вашем наборе инструментов, чем один молоток.

Учти это:

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

против

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

1-й случай приводит к высокому сцеплению и т. Д .; 2-й способ не имеет проблем @ Аарона описывает, насколько я могу судить. Это все о том, как вы используете это.


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

Иногда Ди является излишним для определенной задачи. Метод в примере кода может быть конструктором, но не обязательно - без рассмотрения конкретного примера - это спорный аргумент. Кроме того, DoSomething может взять ISomething, и MySingleton может реализовать этот интерфейс - хорошо, это не в образце ... но это просто образец.
Евгений

1

Сделайте так, чтобы на каждом экране присутствовал менеджер в их конструкторе.

Когда вы запускаете свое приложение, вы создаете один экземпляр менеджера и передаете его.

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.