Есть ли жизнеспособные альтернативы шаблону GOF Singleton?

91

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

Есть ли у них какие-либо конкретные альтернативы шаблону GOF Singleton?

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

Какие еще у вас есть идеи?

РЕДАКТИРОВАТЬ: Я действительно не хочу, чтобы это был еще один пост о том, «как правильно использовать синглтон». Опять же, я ищу способы избежать этого. Для развлечения, хорошо? Думаю, я задаю чисто академический вопрос голосом вашего лучшего трейлера к фильму: «Что мы могли бы сделать в параллельной вселенной, где нет синглтона?»

CodingWithoutComments
источник
Какие? Это ни хорошо, ни плохо, но как его заменить? Для всех, кто говорит, что это хорошо - не участвуйте. Все, кто говорит, что это плохо, докажите это, показав мне, как я могу жить без этого. Для меня это звучит аргументированно.
S.Lott
@CodingWithoutComents: прочитал весь пост. Вот почему у меня возникло чувство «не отвечайте, если вы считаете, что синглтоны в порядке».
S.Lott
2
Ну, если так попалось, прошу прощения. Я думал, что предпринял серьезные шаги, чтобы избежать поляризации. Я думал, что задал вопрос таким образом, что и любители, и ненавистники Singletons могли бы уйти с этим, как программисты, у всех нас есть выбор - что их никогда не бывает только один правильный способ
CodingWithoutComments 02
Если я использую синглтоны, у меня нет возможности рассказать, как их обойти. Для меня это звучит противоречиво.
S.Lott
9
Я использую синглтоны каждый день, но разве это не мешает мне думать, что, возможно, есть способ лучше? Паттерны дизайна существуют всего 14 лет. Считаю ли я их библейской истиной? Перестанем ли мы мыслить нестандартно? Разве мы не пытаемся продвигать дисциплину CS?
CodingWithoutComments 02

Ответы:

77

Алекс Миллер в « Паттернах, которые я ненавижу » цитирует следующее:

"Когда синглтон кажется ответом, я считаю, что часто разумнее:

  1. Создайте интерфейс и реализацию вашего синглтона по умолчанию
  2. Создайте единственный экземпляр вашей реализации по умолчанию на «вершине» вашей системы. Это может быть конфигурация Spring, или код, или определенный различными способами в зависимости от вашей системы.
  3. Передайте один экземпляр в каждый компонент, который в нем нуждается (внедрение зависимостей)
Яцек Шиманский
источник
2
Я знаю, что этому почти 5 лет, но не могли бы вы рассказать об этом подробнее? Я думаю, меня учили, что правильный способ создания синглтона - это интерфейс. Мне было бы интересно, было ли то, что я делаю, «нормально», и не было ли это так ужасно, как мне сказали.
Pureferret 06
97

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

Синглтоны скрывают зависимости.

Почему это важно?

Потому что, если вы скрываете зависимости, вы теряете из виду степень связи.

Вы можете возразить, что

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

проще чем

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

но, по крайней мере, второй API дает понять, кто именно соавторы метода.

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

Расмус Фабер
источник
+1 Для обсуждения корня проблемы, а не только как ее обойти.
Фил
9
В полноценном многоуровневом приложении второй метод часто создает огромное количество ненужного кода. Загрязняя код средних уровней логикой для переноса на более низкие уровни, объекты, которые в противном случае не нужны на этом уровне, разве мы не создаем зависимости, в которых нет необходимости? Скажем, уровень 4 в стеке навигации инициирует фоновое действие, например загрузку файла. Теперь предположим, что вы хотите предупредить пользователя, когда он завершится, но к тому времени пользователь может находиться в совершенно другой части приложения, которая имеет общий уровень 1 с начальным представлением. Тонны ненужного кода ...
Хари Карам Сингх
1
@RasmusFaber Шаблон Singleton не скрывает зависимости. Ваша жалоба на сокрытие зависимостей - это отдельная проблема, связанная с шаблоном. Это правда, что шаблон позволяет легко совершить эту ошибку, но вполне возможно выполнить шаблон IoC и Singleton вместе в гармонии. Например, я могу создать стороннюю библиотеку, которая требует особого управления жизненным циклом ее экземпляров, и любой, кто использует мою библиотеку, должен по-прежнему «передавать» экземпляр моего синглтона своим классам, используя классическую инъекцию зависимостей, а не «небесный крючок» мой синглтон. от каждого pointcut.
Мартин Блисс
@HariKaramSingh, вы можете использовать шаблон подписки / публикации или общесистемную шину событий (которая должна использоваться всеми заинтересованными сторонами, а не синглом), чтобы обойти это.
Виктор Сорокин
4
Также шаблоны pub / sub или наблюдателя могут быть столь же плохими в отношении «потери связи», как это подтвердит любой, кому приходилось выполнять отладку через фреймворк, который сильно на них полагается. По крайней мере, у синглтонов имя вызываемого метода находится в том же месте, что и вызывающий код :) Лично я думаю, что все эти стратегии имеют свое место, и ни одна из них не может быть реализована вслепую. Неряшливые программисты превратят любой шаблон в спагетти, а ответственным кодировщикам не нужно чрезмерно обременять себя идеологическими ограничениями для создания элегантного кода.
Хари Карам Сингх
14

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

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

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

class NeedyClass {

    private ExSingletonClass exSingleton;

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

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

А потом фабрика.

class FactoryOfNeedy {

    private ExSingletonClass exSingleton;

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

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

Поскольку вы создадите экземпляр своей фабрики только один раз, будет единственный экземпляр exSingleton. Каждый раз, когда вы вызываете buildNeedy, новый экземпляр NeedyClass будет связан с exSingleton.

Надеюсь, это поможет. Пожалуйста, отметьте любые ошибки.

seuvitor
источник
5
чтобы убедиться, что ваш Factory создается только тогда, когда вам нужен частный конструктор и определите метод buildNeedy как статический метод.
Жюльен Гренье,
16
Жюльен прав, это исправление имеет фундаментальный недостаток, заключающийся в том, что вы неявно заявляете, что можете создать экземпляр только одной фабрики. Если вы примете необходимые меры предосторожности, чтобы гарантировать создание экземпляра только одной фабрики (как сказал Жюльен), вы получите ... синглтон! На самом деле этот подход просто добавляет ненужный уровень абстракции поверх синглтона.
Боб Сомерс,
Есть другой способ иметь только один экземпляр. Вы можете создать экземпляр своей фабрики только один раз. Компилятор не требует принудительного выполнения этого требования. Это также помогает с юнит-тестами, когда вам нужен другой экземпляр.
102011-07-12
Это лучшее решение, которое я когда-либо видел для проблемы, ошибочно решаемой с помощью синглтонов - добавление зависимостей без загромождения каждого вызова метода (и, следовательно, перепроектирование интерфейса и рефакторинг его зависимостей). Небольшая модификация идеально подходит для моей ситуации, когда не важно, чтобы был только один экземпляр "singleton", но важно, чтобы все использовали один и тот же экземпляр по умолчанию (модификация: вместо использования factory используйте static методы для внешней установки общего экземпляра и использования этого экземпляра во время построения).
meustrus
Таким образом, основное преимущество этого подхода в том, что вам нужно перемещать только один экземпляр объекта (фабрику), а не целую кучу объектов (для которых вы предпочитаете не использовать синглтоны)?
Хари Карам Сингх
9

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

Кай
источник
Есть ссылки по вышеупомянутым темам?
CodingWithoutComments 02
Эти фреймворки кажутся специфичными для java - есть ли другие варианты для конкретного языка? Или языковой агностик?
CodingWithoutComments 02
для .NET: Castle Windsor castleproject.org/container/index.html Microsoft Unity msdn.microsoft.com/en-us/library/cc468366.aspx Unity на самом деле является фреймворком для внедрения зависимостей, но, вероятно, он подойдет для этой темы.
Эрик ван Бракель,
1
почему за это не так много голосов? IOC - идеальное решение, чтобы избежать синглтона.
Sandeep Jindal
@CodingWithoutComments Я знаю, что в PHP есть сервисный контейнер Symfony. Однако у рубиновых парней есть и другие способы внедрить зависимости. У вас есть конкретный язык, который вас интересует?
Bevelopper
9

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

Я не избегаю паттерна Singleton. Либо это уместно, и я им пользуюсь, либо не уместно, и я им не пользуюсь. Я считаю, что это так просто.

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

Томас Оуэнс
источник
Я собирался сказать то же самое, но это не совсем ответ на его вопрос :)
Swati
2
Пожалуйста, укажите в своем ответе, когда это уместно или нет. Довольно, пожалуйста.
CodingWithoutComments 02
Это действительно ответ на вопрос. У меня нет методов, позволяющих избежать использования шаблона Singleton, потому что я не изо всех сил стараюсь избегать его, и я не думаю, что это должен делать любой разработчик.
Томас Оуэнс,
Я не думаю, что можно делать обобщения, когда это уместно или нет. Все зависит от конкретного проекта и во многом зависит от конструкции рассматриваемой системы. Я не использую «правила большого пальца» для подобных решений.
Томас Оуэнс,
Хм. Я не понимаю, почему это отклоняется. Я ответил на вопрос - каковы ваши методы, позволяющие избежать использования шаблона Singleton? - говоря, что у меня нет техник, потому что я не изо всех сил стараюсь избегать шаблонов. Могут ли противники уточнить свои доводы?
Томас Оуэнс,
5

Monostate (описанный в книге Роберта Мартина «Гибкая разработка программного обеспечения») является альтернативой singleton. В этом шаблоне все данные класса статичны, но геттеры / сеттеры нестатичны.

Например:

public class MonoStateExample
{
    private static int x;

    public int getX()
    {
        return x;
    }

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

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

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

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

Марк М
источник
Это заслуживает большего количества голосов; Я пришел сюда из Google в поисках напоминания о MonoState!
mahemoff
@Mark Мне кажется, что этот дизайн очень сложно заблокировать с точки зрения безопасности потоков. Должен ли я создать блокировку экземпляра для каждой статической переменной в MonoStateExample или создать одну блокировку, которую используют все свойства? Оба имеют серьезные разветвления, которые легко решаются с помощью шаблона Singleton.
Мартин Блисс
Этот пример является альтернативой шаблону Singleton, но не решает проблемы шаблона Singleton.
Sandeep Jindal
4

Одноплодная картина существует потому , что существует ситуации , когда один объект необходим для обеспечения набора услуг .

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

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

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

вместо единого глобального

*pseudocode* singletonType.Instance

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

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

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

Поп-каталин
источник
Я не совсем следую вашему примеру обслуживаемых контейнеров. У вас есть онлайн-ресурсы, на которые вы можете ссылаться?
CodingWithoutComments 02
Взгляните на "Castle Project - Windsor Container" castleproject.org/container/index.html . К сожалению, очень сложно найти абстрактные публикации по этой теме.
Pop Catalin
2

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

(хотя я бы сказал, что это неправильный способ использования синглтона в первую очередь)

Дэвид Коэлле
источник
1

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

Этот тип стиля выглядит очень похожим на C, а не на объектный - вы обнаружите, что все объекты, которые определяют вашу систему, будут иметь ссылку на один и тот же MumbleManager.

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

Используйте простой объект и объект фабрики. Фабрика отвечает за контроль экземпляра и деталей простого объекта только с информацией о конфигурации (например, она содержит) и поведением.

Дэвид
источник
1
Разве Factory не часто бывает синглтон? Вот как я обычно вижу реализацию паттернов Factory.
Томас Оуэнс,
0

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

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

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

Mecki
источник
0

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

user18834
источник
0

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

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

И да, я имел в виду слово «полиция», а не «избегать». Одноэлементного элемента не следует избегать (точно так же, как нельзя избегать goto и глобальных переменных). Вместо этого вам следует контролировать его использование и убедиться, что это лучший способ эффективно выполнить то, что вы хотите.

workmad3
источник
1
Я прекрасно понимаю, о чем вы говорите, дружище. И я полностью согласен. Однако ваш ответ по-прежнему не отвечает конкретно на мой вопрос. Я не хотел, чтобы это был еще один вопрос «когда уместно использовать синглтон?» Меня просто интересовали жизнеспособные альтернативы синглтону.
CodingWithoutComments 02
0

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

Манрико Корацци
источник
0

Поскольку я не программировал в объектно-ориентированной среде (например, Java), я не совсем разбираюсь в тонкостях дискуссии. Но я реализовал синглтон в PHP 4. Я сделал это как способ создания обработчика базы данных «черного ящика», который автоматически инициализируется и не требует передачи вызовов функций вверх и вниз в неполной и несколько сломанной структуре.

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

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

статический
источник
-1

Что ты имеешь в виду, как я могу этого избежать?

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

Но нет. Мне не нужно избегать одноэлементного шаблона. Этого просто не возникает.

DrPizza
источник