Singleton: ¿Cómo debería usarse?

Editar: a partir de otra pregunta proporcioné una respuesta que tiene enlaces a un montón de preguntas / respuestas sobre singletons: Más información sobre singletons aquí:

Entonces, he leído el hilo Singletons: ¿un buen diseño o una muleta?
Y la discusión todavía se enfurece.

Veo a Singletons como un patrón de diseño (bueno y malo).

El problema con Singleton no es el Patrón sino los usuarios (lo siento todos). Todos y su padre piensan que pueden implementar uno correctamente (y de las muchas entrevistas que he hecho, la mayoría de las personas no pueden). Además, como todos piensan que pueden implementar un Singleton correcto, abusan del Patrón y lo utilizan en situaciones que no son apropiadas (¡reemplazando las variables globales con Singletons!).

Entonces las preguntas principales que necesitan ser respondidas son:

  • Cuándo deberías usar un Singleton
  • ¿Cómo se implementa un Singleton correctamente?

Mi esperanza para este artículo es que podamos reunirnos en un solo lugar (en lugar de tener que buscar en Google y buscar en varios sitios) una fuente autorizada de cuándo (y luego cómo) usar un Singleton correctamente. También sería apropiado incluir una lista de Antiuso y implementaciones malas comunes que explican por qué no funcionan y para las buenas implementaciones sus debilidades.


Así que ponte a rodar la pelota:
Levantaré mi mano y diré que esto es lo que uso, pero probablemente tenga problemas.
Me gusta el manejo de “Scott Myers” del tema en sus libros “Effective C ++”

Buenas situaciones para usar Singletons (no muchos):

  • Marcos de registro
  • Subprocesos de reciclaje de piscinas
/* * 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; } }; 

DE ACUERDO. Permite obtener algunas críticas y otras implementaciones juntas.
🙂

Todos ustedes están equivocados. Lea la pregunta. Responder:

Use un Singleton si:

  • Necesita tener un único objeto de un tipo en el sistema

No use un Singleton si:

  • Quieres guardar memoria
  • Quieres probar algo nuevo
  • Quieres alardear de lo mucho que sabes
  • Porque todos los demás lo están haciendo (ver progtwigdor de culto de carga en wikipedia)
  • En widgets de interfaz de usuario
  • Se supone que es un caché
  • En cuerdas
  • En Sesiones
  • Puedo ir todo el día

Cómo crear el mejor singleton:

  • Cuanto más pequeño, mejor. Soy un minimalista
  • Asegúrate de que sea seguro para subprocesos
  • Asegúrate de que nunca sea nulo
  • Asegúrate de que se crea solo una vez
  • ¿Inicialización lenta o del sistema? Hasta sus requisitos
  • A veces, el sistema operativo o la JVM crea singletons para usted (por ejemplo, en Java, cada definición de clase es un singleton)
  • Proporcionar un destructor o de alguna manera averiguar cómo disponer de los recursos
  • Usa poca memoria

Los singleton te dan la capacidad de combinar dos rasgos malos en una clase. Eso está mal en casi todos los sentidos.

Un singleton te da:

  1. Acceso global a un objeto, y
  2. Una garantía de que nunca más se puede crear un objeto de este tipo

El número uno es directo. Globales son generalmente malos. Nunca debemos hacer que los objetos sean accesibles globalmente a menos que realmente lo necesitemos.

El número dos puede parecer que tiene sentido, pero pensemos en ello. ¿Cuándo fue la última vez que accidentalmente * creó un objeto nuevo en lugar de hacer referencia a uno existente? Como esto está etiquetado como C ++, usemos un ejemplo de ese idioma. ¿Sueles escribir accidentalmente

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

Cuando tu intención era escribir

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

Por supuesto no. No necesitamos protección contra este error, porque ese tipo de error simplemente no ocurre. Si lo hace, la respuesta correcta es irse a casa y dormir durante 12-20 horas y esperar que se sienta mejor.

Si solo se necesita un objeto, simplemente crea una instancia. Si un objeto debe ser accesible globalmente, conviértalo en global. Pero eso no significa que debería ser imposible crear otras instancias de él.

La restricción de "solo una instancia es posible" realmente no nos protege contra posibles errores. Pero hace que nuestro código sea muy difícil de refactorizar y mantener. Porque a menudo descubrimos más tarde que necesitábamos más de una instancia. Tenemos más de una base de datos, tenemos más de un objeto de configuración, queremos varios registradores. Es posible que nuestras pruebas unitarias sean capaces de crear y recrear estos objetos en cada prueba, para tomar un ejemplo común.

Por lo tanto, se debe usar un singleton si, y solo si, necesitamos los dos rasgos que ofrece: si necesitamos acceso global (lo cual es raro, porque generalmente se desaconsejan los globales) y debemos evitar que alguien cree más de una instancia de un clase (que me parece un problema de diseño). La única razón que puedo ver para esto es si la creación de dos instancias corrompería nuestro estado de aplicación, probablemente porque la clase contiene una cantidad de miembros estáticos o tonterías similares. En cuyo caso, la respuesta obvia es arreglar esa clase. No debería depender de ser la única instancia.

Si necesita acceso global a un objeto, hágalo global, como std::cout . Pero no limite el número de instancias que se pueden crear.

Si es absolutamente necesario que limite el número de instancias de una clase a solo una, y no hay forma de que la creación de una segunda instancia pueda manejarse de manera segura, entonces haga cumplir eso. Pero no lo hagas accesible globalmente también.

Si necesita ambos rasgos, entonces 1) conviértalo en un singleton, y 2) dígame para qué lo necesita, porque me está costando imaginar un caso así.

El problema con los singletons no es su implementación. Es que combinan dos conceptos diferentes, ninguno de los cuales es obviamente deseable.

1) Singletons proporcionan un mecanismo de acceso global a un objeto. A pesar de que pueden ser marginalmente más seguros en cuanto a subprocesos o marginalmente más confiables en idiomas sin un orden de inicialización bien definido, este uso sigue siendo el equivalente moral de una variable global. Es una variable global disfrazada de una syntax incómoda (foo :: get_instance () en lugar de g_foo, por ejemplo), pero cumple el mismo propósito (un único objeto accesible en todo el progtwig) y tiene exactamente los mismos inconvenientes.

2) Singletons previene múltiples instancias de una clase. Es raro, IME, que este tipo de característica se cueza en una clase. Normalmente es una cosa mucho más contextual; muchas de las cosas que se consideran como una y solo son realmente solo-pasa-a-ser-uno. IMO, una solución más adecuada es crear solo una instancia, hasta que se dé cuenta de que necesita más de una instancia.

Una cosa con los patrones: no generalices . Tienen todos los casos cuando son útiles y cuando fallan.

Singleton puede ser desagradable cuando tienes que probar el código. Por lo general, está atascado con una instancia de la clase, y puede elegir entre abrir una puerta en el constructor o algún método para restablecer el estado, y así sucesivamente.

Otro problema es que Singleton de hecho no es más que una variable global disfrazada. Cuando tienes demasiado estado global compartido sobre tu progtwig, las cosas tienden a retroceder, todos lo sabemos.

Puede hacer que el seguimiento de la dependencia sea más difícil. Cuando todo depende de su Singleton, es más difícil cambiarlo, dividirlo en dos, etc. Por lo general, está atascado con eso. Esto también dificulta la flexibilidad. Investigue algún marco de dependency injections para tratar de aliviar este problema.

Singletons básicamente le permite tener un estado global complejo en lenguajes que de otro modo harían difícil o imposible tener variables globales complejas.

Java en particular utiliza singletons como reemplazo de variables globales, ya que todo debe estar contenido dentro de una clase. Lo más parecido a las variables globales son las variables públicas estáticas, que se pueden usar como si fueran globales con import static

C ++ tiene variables globales, pero el orden en que se invocan los constructores de las variables de clase globales no está definido. Como tal, un singleton le permite diferir la creación de una variable global hasta la primera vez que se necesite esa variable.

Los lenguajes como Python y Ruby utilizan muy poco los singleton porque en su lugar puede usar variables globales dentro de un módulo.

Entonces, ¿cuándo es bueno / malo usar un singleton? Más o menos exactamente cuándo sería bueno / malo usar una variable global.

El diseño moderno de C ++ de Alexandrescu tiene un singleton genérico hereditario seguro para subprocesos.

Para mi valor de 2p, creo que es importante tener vidas definidas para tus singletons (cuando es absolutamente necesario usarlos). Normalmente no dejo que la función estática get() instancia nada, y dejo la configuración y la destrucción en alguna sección dedicada de la aplicación principal. Esto ayuda a resaltar las dependencias entre los singletons, pero, como se destacó anteriormente, es mejor evitarlos si es posible.

  • ¿Cómo se implementa un Singleton correctamente?

Hay un problema que nunca he mencionado, algo con lo que me encontré en un trabajo anterior. Tuvimos singletons en C ++ que se compartieron entre las DLL, y la mecánica habitual de garantizar una sola instancia de una clase simplemente no funciona. El problema es que cada DLL obtiene su propio conjunto de variables estáticas, junto con el EXE. Si su función get_instance está en línea o es parte de una biblioteca estática, cada DLL terminará con su propia copia del “singleton”.

La solución es asegurarse de que el código singleton esté definido solo en una DLL o EXE, o cree un administrador singleton con esas propiedades para plotr las instancias.

El primer ejemplo no es seguro para subprocesos: si dos subprocesos llaman a getInstance al mismo tiempo, ese static será un PITA. Alguna forma de mutex ayudaría.

Como han señalado otros, las principales desventajas de los singleton incluyen la imposibilidad de ampliarlos y la pérdida de la capacidad de instanciar más de una instancia, por ejemplo, para fines de prueba.

Algunos aspectos útiles de singletons:

  1. instanciación lenta o inicial
  2. práctico para un objeto que requiere configuración y / o estado

Sin embargo, no tiene que usar un singleton para obtener estos beneficios. Puede escribir un objeto normal que hace el trabajo, y luego hacer que las personas accedan a él a través de una fábrica (un objeto separado). La fábrica puede preocuparse por solo instanciar una, y reutilizarla, etc., si es necesario. Además, si progtwig en una interfaz en lugar de una clase concreta, la fábrica puede usar estrategias, es decir, puede activar y desactivar varias implementaciones de la interfaz.

Finalmente, una fábrica se presta a tecnologías de dependency injection como Spring, etc.

Los singletons son útiles cuando se ejecuta un gran número de código cuando se inicializa y se opone. Por ejemplo, cuando usa iBatis cuando configura un objeto de persistencia tiene que leer todas las configuraciones, analizar los mapas, asegurarse de que todo es correcto, etc. antes de llegar a su código.

Si hicieras esto cada vez, el rendimiento se degradaría mucho. Al usarlo en un singleton, tomas ese golpe una vez y luego todas las llamadas subsecuentes no tienen que hacerlo.

La verdadera caída de Singletons es que rompen la herencia. No puede derivar una nueva clase para darle funcionalidad extendida a menos que tenga acceso al código donde se hace referencia al Singleton. Por lo tanto, más allá del hecho de que Singleton hará que su código esté bien conectado (corregible mediante un Patrón de estrategia … también conocido como Dependency Injection), también evitará que cierre secciones del código de revisión (bibliotecas compartidas).

Por lo tanto, incluso los ejemplos de registradores o grupos de hilos no son válidos y deberían ser reemplazados por estrategias.

La mayoría de las personas usa singleton cuando intenta sentirse a gusto con el uso de una variable global. Existen usos legítimos, pero la mayoría de las veces cuando las personas los usan, el hecho de que solo puede haber una instancia es solo un hecho trivial en comparación con el hecho de que es accesible a nivel mundial.

Debido a que un singleton solo permite que se cree una instancia, esta controla efectivamente la replicación de instancias. por ejemplo, no necesitarías múltiples instancias de una búsqueda, por ejemplo, un mapa de búsqueda morse, por lo tanto, ajustarlo en una clase singleton es apto. Y solo porque tenga una sola instancia de la clase no significa que también tenga limitaciones en el número de referencias a esa instancia. Puede poner en cola las llamadas (para evitar problemas de subprocesos) a la instancia y los cambios de efecto necesarios. Sí, la forma general de un singleton es globalmente pública, sin duda puede modificar el diseño para crear un singleton con acceso más restringido. No he cansado esto antes, pero estoy seguro de que es posible. Y a todos aquellos que comentaron que decir que el patrón singleton es completamente malo, deberían saber esto: sí, es malo si no lo usan correctamente o dentro de los límites de funcionalidad efectiva y comportamiento predecible: no lo GENERALICEN.

Pero cuando necesito algo como un Singleton, a menudo termino usando un contador Schwarz para crear una instancia.

Uso Singletons como prueba de entrevista.

Cuando le pido a un desarrollador que nombre algunos patrones de diseño, si todo lo que pueden nombrar es Singleton, no se contratan.

A continuación se muestra el mejor enfoque para implementar un patrón singleton seguro para hilos con la desasignación de la memoria en el propio destructor. Pero creo que el destructor debería ser opcional porque la instancia singleton se destruirá automáticamente cuando el progtwig finalice:

 #include #include 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 lock(mtx); if (singletonInstance == NULL) singletonInstance = new MySingleton(); } return singletonInstance; } 

Con respecto a las situaciones en las que necesitamos usar clases singleton podemos: Si queremos mantener el estado de la instancia durante la ejecución del progtwig. Si estamos involucrados en escribir en el registro de ejecución de una aplicación donde solo una instancia del archivo necesita ser usado … y así sucesivamente. Será apreciable si alguien puede sugerir la optimización en mi código anterior.

Antiuso:

Un problema importante con el uso excesivo de singleton es que el patrón evita la extensión e intercambio fáciles de implementaciones alternativas. El nombre de clase está codificado de forma rígida allí donde se utiliza el singleton.

Creo que esta es la versión más robusta para 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(); } } } } 

Aquí está la versión optimizada para .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(); } } } } 

Puede encontrar este patrón en dotfactory.com .

El patrón Singleton de Meyers funciona bastante bien la mayor parte del tiempo, y en las ocasiones que lo hace no necesariamente paga para buscar algo mejor. Siempre que el constructor nunca arroje y no haya dependencias entre singletons.

Un singleton es una implementación para un objeto accesible globalmente (GAO a partir de ahora) aunque no todos los GAO son singletons.

Los propios madereros no deben ser solteros, pero los medios para iniciar sesión deberían idealmente tener acceso global, desacoplarse donde se genera el mensaje de registro desde dónde o cómo se registra.

La carga lenta / evaluación diferida es un concepto diferente y singleton generalmente implementa eso también. Viene con muchos problemas propios, en particular, seguridad de hilos y problemas si falla con excepciones tales que lo que parecía una buena idea en ese momento no es tan bueno después de todo. (Un poco como la implementación de COW en cadenas).

Con esto en mente, los GOA se pueden inicializar así:

 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; } 

No es necesario hacerlo de una manera tan simple como esa, y claramente en una biblioteca cargada que contiene objetos, es probable que desee algún otro mecanismo para administrar su vida útil. (Put them in an object that you get when you load the library).

As for when I use singletons? I used them for 2 things – A singleton table that indicates what libraries have been loaded with dlopen – A message handler that loggers can subscribe to and that you can send messages to. Required specifically for signal handlers.

I still don’t get why a singleton has to be global.

I was going to produce a singleton where I hid a database inside the class as a private constant static variable and make class functions that utilize the database without ever exposing the database to the user.

I don’t see why this functionality would be bad.

I find them useful when I have a class that encapsulates a lot of memory. For example in a recent game I’ve been working on I have an influence map class that contains a collection of very large arrays of contiguous memory. I want that all allocated at startup, all freed at shutdown and I definitely want only one copy of it. I also have to access it from many places. I find the singleton pattern to be very useful in this case.

I’m sure there are other solutions but I find this one very useful and easy to implement.

If you are the one who created the singleton and who uses it, dont make it as singleton (it doesn’t have sense because you can control the singularity of the object without making it singleton) but it makes sense when you a developer of a library and you want to supply only one object to your users (in this case you are the who created the singleton, but you aren’t the user).

Singletons are objects so use them as objects, many people accesses to singletons directly through calling the method which returns it, but this is harmful because you are making your code knows that object is singleton, I prefer to use singletons as objects, I pass them through the constructor and I use them as ordinary objects, by that way, your code doesn’t know if these objects are singletons or not and that makes the dependencies more clear and it helps a little for refactoring …

In desktop apps (I know, only us dinosaurs write these anymore!) they are essential for getting relatively unchanging global application settings – the user language, path to help files, user preferences etc which would otherwise have to propogate into every class and every dialog.

Edit – of course these should be read-only !

Another implementation

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