Синглтон: как его использовать

Изменить:  Из другого вопроса я дал ответ, который содержит ссылки на множество вопросов/ответов о синглонах: Подробнее о синглонах здесь:

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

Я вижу синглтоны как шаблон дизайна (хороший и плохой).

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

Таким образом, основными вопросами, на которые нужно ответить, являются:

  • Когда вы должны использовать Singleton
  • Как вы правильно реализуете Singleton

Моя надежда на эту статью состоит в том, что мы можем собирать вместе в одном месте (вместо того, чтобы иметь Google и искать несколько сайтов) авторитетным источником того, когда (а затем как) правильно использовать Singleton. Также уместно будет список Анти-Использование и распространенные плохие реализации, объясняющие, почему они не работают и для хороших реализаций их слабости.


Так получилось, что мяч катится:
Я подниму руку и скажу, что это то, что я использую, но, вероятно, проблемы.
Мне нравится "Скотт Майерс", обрабатывающий предмет в своих книгах "Эффективный С++"

Хорошие ситуации для использования синглтонов (не так много):

  • Структуры ведения журнала
  • Пулы для регенерации потоков.
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

OK. Давайте рассмотрим некоторые критические замечания и другие реализации.
: -)

Ответ 1

Все вы ошибаетесь. Прочтите вопрос. Ответ:

Использовать Singleton, если:

  • Если вам нужен один и только один объект типа в системе

Не используйте Singleton, если:

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

Как создать лучший синглтон:

  • Чем меньше, тем лучше. Я минималистский
  • Убедитесь, что он защищен потоками.
  • Удостоверьтесь, что он никогда не был нулевым.
  • Убедитесь, что он создан только один раз
  • Lazy или инициализация системы? До ваших требований
  • Иногда ОС или JVM создают для вас одиночные списки (например, в Java каждое определение класса является одиночным)
  • Предоставить деструктор или как-то выяснить, как распоряжаться ресурсами.
  • Используйте небольшую память

Ответ 2

Синглоты дают вам возможность сочетать две плохие черты в одном классе. Это неправильно во всех отношениях.

Синглтон дает вам:

  • Глобальный доступ к объекту и
  • Гарантирует, что можно создать не более одного объекта этого типа.

Номер один прост. Глобалы, как правило, плохие. Мы никогда не должны делать объекты глобально доступными, если мы действительно не нуждаемся в этом.

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

std::ostream os;
os << "hello world\n";

Когда вы намеревались написать

std::cout << "hello world\n";

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

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

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

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

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

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

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

Ответ 3

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

1) Синглтоны обеспечивают глобальный механизм доступа к объекту. Хотя они могут быть немного более потокобезопасными или немного более надежными на языках без четко определенного порядка инициализации, это использование по-прежнему является моральным эквивалентом глобальной переменной. Это глобальная переменная, одетая в какой-то неловкий синтаксис (foo:: get_instance() вместо g_foo, скажем), но она выполняет ту же самую цель (один объект, доступный по всей программе) и имеет те же самые недостатки.

2) Синглеты предотвращают множественные экземпляры класса. Редко, IME, эта особенность должна быть испечена в класс. Это обычно гораздо более контекстуальная вещь; многие вещи, которые считаются одними и теми же, действительно просто случаются, чтобы быть единственными. IMO - более подходящее решение - просто создать только один экземпляр - пока вы не поймете, что вам нужно больше одного экземпляра.

Ответ 4

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

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

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

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

Ответ 5

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

Java, в частности, использует синглтоны в качестве замены глобальных переменных, поскольку все должно содержаться внутри класса. Ближе всего к глобальным переменным относятся общедоступные статические переменные, которые могут использоваться как глобальные с помощью import static

С++ имеет глобальные переменные, но порядок, в котором вызывается конструкторы глобальных переменных класса, - undefined. Таким образом, singleton позволяет отложить создание глобальной переменной до тех пор, пока не понадобится эта переменная.

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

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

Ответ 6

Современный дизайн С++ от Alexandrescu имеет потокобезопасный, наследуемый общий синглтон.

Для моей 2p-ценности я считаю важным иметь определенные сроки жизни для ваших синглетов (когда это абсолютно необходимо для их использования). Обычно я не допускаю, чтобы статическая функция get() создавала экземпляр чего-либо и оставляла настройку и уничтожение в какой-то выделенный раздел основного приложения. Это помогает выявить зависимости между синглтонами, но, как подчеркивалось выше, лучше всего избегать их, если это возможно.

Ответ 7

  • Как вы правильно реализуете Singleton

Есть одна проблема, о которой я никогда не упоминал, что-то, с чем я столкнулся на предыдущей работе. У нас были синглтоны С++, которые были разделены между DLL, и обычная механика обеспечения одного экземпляра класса просто не работает. Проблема в том, что каждая DLL получает свой собственный набор статических переменных вместе с EXE. Если ваша функция get_instance является встроенной или частью статической библиотеки, каждая DLL завершит свою собственную копию "singleton".

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

Ответ 8

Первый пример не является потокобезопасным - если два потока одновременно называют getInstance, этот статический файл будет PITA. Некоторая форма мьютекса поможет.

Ответ 9

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

Некоторые полезные аспекты синглетонов:

  • ленивый или предварительный экземпляр
  • удобно для объекта, который требует установки и/или состояния

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

Наконец, factory предоставляет технологии впрыскивания зависимостей, такие как Spring и т.д.

Ответ 10

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

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

Ответ 11

Реальное падение Синглтонов состоит в том, что они нарушают наследование. Вы не можете получить новый класс, чтобы предоставить вам расширенную функциональность, если у вас нет доступа к коду, на который ссылается Singleton. Таким образом, помимо того, что Синглтон сделает ваш код плотно связанным (исправляется с помощью шаблона стратегии... aka Dependency Injection), он также не позволит вам закрыть разделы кода из ревизии (разделяемые библиотеки).

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

Ответ 12

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

Ответ 13

Поскольку singleton позволяет только создать один экземпляр, он эффективно контролирует репликацию экземпляра. например, вам не понадобятся несколько экземпляров lookup - например, карта поиска morse, поэтому ее упаковка в одноэлементном классе является apt. И только потому, что у вас есть один экземпляр класса, это не значит, что вы также ограничены количеством ссылок на этот экземпляр. Вы можете отправлять вызовы в очередь (чтобы избежать проблем с потоками) в случае необходимости изменений в экземпляре и эффекте. Да, общая форма синглтона является общедоступной, вы можете, конечно, изменить дизайн, чтобы создать более ограниченный доступ к Singleton. Я не устал от этого раньше, но я уверен, что это возможно. И всем тем, кто прокомментировал синглтонную картину, совершенно злой, вы должны это знать: да, это зло, если вы не используете его должным образом или внутри него, ограничиваясь эффективной функциональностью и предсказуемым поведением: не ОБЩАЯ.

Ответ 14

Но когда мне нужно что-то вроде Singleton, я часто получаю Schwarz Counter для его создания.

Ответ 15

Я использую Singletons в качестве теста на собеседование.

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

Ответ 16

Анти-Использование:

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

Ответ 17

Я думаю, что это самая надежная версия для С#:

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Вот оптимизированная .NET версия:

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Этот шаблон можно найти в dotfactory.com.

Ответ 18

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

Синтаксис - это реализация для доступного по всему миру объекта (GAO с этого момента), хотя не все GAO являются одиночными.

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

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

С учетом этого GOAs могут быть инициализированы следующим образом:

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

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

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

Ответ 19

Я все еще не понимаю, почему одноэлемент должен быть глобальным.

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

Я не понимаю, почему эта функция была бы плохой.

Ответ 20

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

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

Ответ 21

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

Изменить - конечно, они должны быть доступны только для чтения!

Ответ 22

Другая реализация

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};