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


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

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

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

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

Итак, основные вопросы, на которые нужно ответить:

  • когда вы должны использовать одноэлементный
  • как реализовать Синглтон правильно

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


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

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

  • Logging Framework
  • пулы потоков переработки
/*
 * 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;
        }
};

ОК. Давайте получим некоторую критику и другие реализации вместе.
: -)

24 276

24 ответа:

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

используйте Синглтон, если:

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

Не используйте Синглтон, если:

  • вы хотите сохранить
  • вы хотите попробовать что-то новое
  • вы хотите показать, как много вы знаете
  • потому что все остальные делают это (см. cargo cult programmer in wikipedia)
  • в виджетах пользовательского интерфейса
  • предполагается, что это кэш
  • в строке
  • На Сессии
  • Я могу идти весь день

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

для моего 2P-worth, я думаю, что важно определить время жизни для ваших синглетов (когда это абсолютно необходимо использовать их). Я обычно не позволяю статический!--0--> функция создает экземпляр чего-либо и оставляет настройку и разрушение в каком-то специальном разделе основного приложения. Это помогает выделить зависимости между синглетами-но, как подчеркивалось выше, лучше всего просто избегать их, если это возможно.

  • Как правильно реализовать Синглтон

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

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

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

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

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

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

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

В конце концов, фабрика одалживает к технологиям впрыски зависимости как весна ЕТК.

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

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

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

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

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

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

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

Я использую синглтоны в качестве теста интервью.

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

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

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

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

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

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

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

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.

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

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

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

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

С имея это в виду, 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;
}

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

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

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

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

Я не понимаю, почему эта функциональность будет плохо.

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

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

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

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

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

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

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

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

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};