Singleton: ¿Cómo se debe usar?


291

Editar: De otra pregunta, proporcioné una respuesta que tiene enlaces a muchas preguntas / respuestas sobre singletons: Más información sobre singletons aquí:

Así que he leído el hilo Singletons: ¿buen diseño o una muleta?
Y la discusión aún continúa.

Veo 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 la gente no puede). Además, como todos piensan que pueden implementar un Singleton correcto, abusan del Patrón y lo usan en situaciones que no son apropiadas (¡reemplazando las variables globales con Singletons!).

Entonces, las principales preguntas que deben responderse son:

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

Mi esperanza para este artículo es que podamos reunir 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 una lista de Anti-Usos e implementaciones erróneas comunes que explican por qué no funcionan y para las buenas implementaciones sus debilidades.


Así que ponte en marcha:
levantaré la mano y diré que esto es lo que uso, pero que probablemente tenga problemas.
Me gusta el manejo de "Scott Myers" del tema en sus libros "Efectivo C ++"

Buenas situaciones para usar Singletons (no muchos):

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

OKAY. Vamos a obtener algunas críticas y otras implementaciones juntas.
:-)


36
¿Qué pasa si luego decides que quieres varios registradores? O múltiples grupos de subprocesos? Si solo desea un registrador, solo cree una instancia y hágalo global. Los Singletons solo son buenos si NECESITAS absolutamente allí para ser uno solo y NECESITA ser global, en mi humilde opinión.

3
Quién dijo que un marco solo puede tener 1 instancia de registrador. Un singelton que representa el marco. Framwork puede proporcionarle registradores específicos.
Martin York

Sí. No usaría un singeltong como un conjunto de hilos. Simplemente arrojando ideas para generar respuestas.
Martin York

@Dan Singleton que implementa el patrón de estrategia. El comportamiento se abstrae de singleton. Singleton es un único punto de entrada. No tenga dos registradores, tenga un registrador que pueda decidir cómo iniciar sesión. No solo puede generar un registro a la vez, no es necesario tener dos.
Lee Louviere

55
Xaade: ¿y si quieres iniciar sesión en dos archivos? O a una base de datos? O una toma de red? ¿O un widget GUI? El punto es, no agregue restricciones artificiales, no hay necesidad de hacerlo. ¿Con qué frecuencia ha creado accidentalmente dos para bucles en lugar de solo uno? Si solo desea un registrador, cree solo uno.

Respuestas:


182

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

Use un Singleton si:

  • Necesita tener uno y solo un objeto de un tipo en el sistema

No use un Singleton si:

  • Quieres ahorrar memoria
  • Quieres probar algo nuevo
  • Quieres mostrar cuánto sabes
  • Porque todos los demás lo están haciendo (ver programador 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 dia

Cómo crear el mejor singleton:

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

14
En realidad, creo que tampoco estás del todo correcto. Lo reformularía como: "Si tú necesita tener uno y solo un objeto de un tipo en el sistema Y necesita tener acceso global a él" El énfasis en la necesidad es mío, no lo haga si es conveniente, solo si usted Debe tener.

9191
Tú también te equivocas. Si necesita uno y solo un objeto, cree uno y solo uno. Si no hay una forma lógica de que dos instancias puedan ser acomodadas sin dañar irreversiblemente la aplicación, debería considerar convertirla en un singleton. Y luego está el otro aspecto, el acceso global: si no necesita acceso global a la instancia, no debería ser un singleton.
jalf

44
Cerrado por modificación, abierto por extensión. El problema es que no puedes extender un singleton para que sea un duoton o un tripleton. Está atascado como un singleton.
Lee Louviere

2
@ enzom83: Un Singleton con mayúscula incluye código para garantizar su solidez. Si solo desea una instancia, puede perder ese código y simplemente crear una instancia usted mismo ... dándole el ahorro de memoria de una sola instancia, además de los ahorros de la omisión del código de cumplimiento único, lo que también significa no sacrificar el capacidad de crear una segunda instancia si alguna vez cambian sus requisitos.
cHao

44
"Si necesita tener un solo objeto de un tipo en el sistema" - "... y nunca quiere burlarse de ese objeto en una prueba unitaria".
Cygon

72

Los Singletons te dan la posibilidad 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 no se puede crear más de un objeto de este tipo

El número uno es sencillo. Los globales son generalmente malos. Nunca debemos hacer que los objetos sean globalmente accesibles 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 nuevo objeto en lugar de hacer referencia a uno existente? Como esto está etiquetado como C ++, usemos un ejemplo de ese lenguaje. ¿Escribes a menudo accidentalmente?

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

Cuando pretendías 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 es así, 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 cree una instancia. Si un objeto debe ser accesible globalmente, conviértalo en global. Pero eso no significa que deba ser imposible crear otras instancias del mismo.

La restricción "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. Nosotros hacemos tener más de una base de datos, que tiene más de un objeto de configuración, sí queremos varios registradores. Nuestras pruebas unitarias pueden querer poder crear y recrear estos objetos en cada prueba, para tomar un ejemplo común.

Por lo tanto un conjunto unitario debe utilizarse si y sólo si, necesitamos tanto los rasgos que ofrece: Si necesitamos acceso global (que es raro, porque globales son generalmente desalentados) y que necesitamos para evitar que alguien alguna vez la creación de más de una instancia de una clase (que me parece un problema de diseño). La única razón por la que puedo ver esto es si crear dos instancias dañaría el estado de nuestra aplicación, probablemente porque la clase contiene varios 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, conviértalo en global, como std::cout . Pero no restrinja el número de instancias que se pueden crear.

Si realmente necesita restringir el número de instancias de una clase a solo una, y no hay forma de que crear una segunda instancia pueda manejarse de manera segura, entonces haga cumplir eso. Pero tampoco lo haga accesible a nivel mundial.

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


3
o podría convertirlo en global y obtener solo uno de los inconvenientes de un singleton. Con el singleton, se limitaría simultáneamente a una instancia de esa clase de base de datos. ¿Por qué hacer eso? O podría ver por qué tiene tantas dependencias que la lista de instanciación se vuelve "realmente larga". ¿Son todos necesarios? ¿Deberían delegarse algunos de ellos en otros componentes? Quizás algunos de ellos podrían agruparse en una estructura para que podamos pasarlos como un argumento único. Hay muchas soluciones, todas ellas mejores que los singletons.
jalf

66
Sí, un singleton podría estar justificado allí. Pero creo que acabas de demostrar mi punto de que solo es necesario en casos bastante exóticos. La mayoría del software no se ocupa del hardware de quitanieves. Pero todavía no estoy convencido. Estoy de acuerdo en que en tu aplicación real solo quieres uno de estos. Pero, ¿qué pasa con las pruebas unitarias? Cada uno de ellos debería ejecutarse de forma aislada, por lo que idealmente deberían crear su propio SpreaderController, que es difícil de hacer con un singleton. Finalmente, ¿por qué sus compañeros de trabajo crearían múltiples instancias en primer lugar? ¿Es ese un escenario realista contra el cual protegerse?
jalf

3
Y un punto que se perdió es que, si bien sus dos últimos ejemplos posiblemente justifiquen la limitación de "una sola instancia", no hacen nada para justificar la "accesible globalmente". ¿Por qué demonios toda la base de código debería poder acceder a la unidad de administración de su conmutador telefónico? El punto en un singleton es darte ambos rasgos. Si solo necesita uno u otro, no debe usar un singleton.
jalf

2
@ jalf - Mi objetivo era solo darte un ejemplo de dónde Singleton es útil en la naturaleza, ya que no podías imaginar ninguno; Supongo que no ves muchas veces aplicarlo en tu línea actual de trabajo. Cambié a la programación de quitanieves desde aplicaciones comerciales únicamente porque me permitiría usar Singleton. :) j / k Estoy de acuerdo con tu premisa de que hay mejores maneras de hacer estas cosas, me has dado mucho en qué pensar. Gracias por la discusión!
J. Polfer el

2
Usar el "patrón" singleton ( AHEM! ) Para evitar que las personas instalen más instancias es una estupidez simplemente para evitar que las personas lo hagan de forma accesoria. Cuando tengo una variable local foo1 de tipo Foo en mi pequeña función y solo quiero una en la función, no me preocupa que alguien vaya a crear una segunda Foo2 Foo2 y usarla en lugar de la original.
Thomas Eding

36

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

1) Los Singletons proporcionan un mecanismo de acceso global a un objeto. Aunque podrían ser marginalmente más seguros 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 vestida con una sintaxis incómoda (foo :: get_instance () en lugar de g_foo, por ejemplo), pero tiene exactamente el mismo propósito (un solo objeto accesible en todo el programa) y tiene exactamente los mismos inconvenientes.

2) Singletons previenen múltiples instancias de una clase. Es raro, IME, que este tipo de característica se incluya en una clase. Normalmente es una cosa mucho más contextual; Muchas de las cosas que se consideran como uno y solo uno son realmente solo para ser uno solo. En mi opinión, una solución más adecuada es crear solo una instancia, hasta que se dé cuenta de que necesita más de una instancia.


66
Convenido. Dos errores pueden hacer un derecho según algunos, en el mundo real. Pero en la programación, mezclar dos malas ideas no da como resultado una buena.
jalf

27

Una cosa con los patrones: no generalizar . 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, etc.

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

Puede dificultar el seguimiento de dependencias . Cuando todo depende de su Singleton, es más difícil cambiarlo, dividirlo en dos, etc. Por lo general, está atrapado en él. Esto también dificulta la flexibilidad. Investigue un marco de inyección de dependencia para tratar de aliviar este problema.


8
No, un singleton es mucho más que una variable global disfrazada. Eso es lo que lo hace especialmente malo. Combina la globalidad (que generalmente es mala) con otro concepto que también es malo (el de no dejar que el programador cree una instancia si decide que necesita una instancia). A menudo se usan como variables globales, sí. Y luego arrastran el otro efecto secundario desagradable también, y paralizan la base de código.
jalf

77
También se debe tener en cuenta que los singletons no tienen que tener accesibilidad pública. Un singleton puede muy bien ser interno a la biblioteca y nunca expuesto al usuario. Por lo tanto, no son necesariamente "globales" en ese sentido.
Steven Evers

1
+1 por señalar cuánto dolor tienen los Singletons en las pruebas.
DevSolar

1
@jalf No permitir que alguien cree más de una instancia de una clase no es algo malo. Si realmente solo hay una instancia de la clase instanciada que haga cumplir el requisito. Si alguien decide más tarde que necesita crear otra instancia, debería refactorizarla, porque en primer lugar nunca debería haber sido un singleton.
William

2
@William: y tuve que tener varios registradores de vez en cuando. No estás discutiendo por un singleton, sino por un simple local viejo. Desea saber que siempre hay un registrador disponible. Para eso es global. No necesita saber que ningún otro registrador puede ser instanciado , que es lo que impone un singleton. (intente escribir pruebas unitarias para su registrador; eso es mucho más fácil si puede crearlo y destruirlo según sea necesario, y eso no es posible con un singleton)
jalf

13

Los Singletons básicamente le permiten tener un estado global complejo en idiomas que, de lo contrario, hacen que sea difícil o imposible tener variables globales complejas.

Java en particular usa 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 pueden usarse como si fueran globales conimport static

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

Los lenguajes como Python y Ruby usan singletons muy poco porque en su lugar puedes usar variables globales dentro de un módulo.

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


¿Cuándo alguna vez una variable global es "buena"? A veces son la mejor solución para un problema, pero nunca son "buenos".
DevSolar

1
La variable global es buena cuando se usa en todas partes, y todo puede tener acceso a ella. La implementación de una máquina de un solo estado puede hacer uso de un singleton.
Lee Louviere

Me gusta la capa de indirección en esta respuesta: "cuándo sería bueno / malo usar un global". Tanto DevSolar como Lee Louviere obtienen el valor con el que están de acuerdo, aunque en el momento de la respuesta no se pudo saber quién haría comentarios.
Praxeolítico

6

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

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


6
  • ¿Cómo implementas un Singleton correctamente?

Hay un problema que nunca he visto mencionado, algo con lo que me encontré en un trabajo anterior. Teníamos singletons en C ++ que se compartían 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 solo esté definido en una DLL o EXE, o crear un administrador singleton con esas propiedades para parcelar instancias.


10
Hola, escuché que te gustaban los Singletons, así que hice un Singleton para tu Singleton, para que puedas anti-patrón mientras anti-patrón.
Eva

@Eva, sí, algo así. No creé el problema, solo tenía que hacerlo funcionar de alguna manera.
Mark Ransom

5

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


Sí, eso se observa en los comentarios anteriores: * Limitación: diseño de rosca única * Ver: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf * Para problemas asociados con el bloqueo en aplicaciones de subprocesos múltiples
Martin York

El singleton clásico con solo getInstance como método estático y métodos de instancia para otras operaciones nunca se puede hacer seguro para subprocesos. (bueno, a menos que lo convierta en una sola tonelada por hilo usando almacenamiento local de hilos ...)
Tobi

incluso en c ++ 11 o posterior?
hg_git

5

Como otros han señalado, las principales desventajas de los singletons incluyen la incapacidad de extenderlos y perder el poder de crear instancias en más de una instancia, por ejemplo, con fines de prueba.

Algunos aspectos útiles de los singletons:

  1. instanciación perezosa o inicial
  2. útil 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 haga 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 crear una instancia y reutilizarla, etc., si es necesario. Además, si programa en una interfaz en lugar de una clase concreta, la fábrica puede usar estrategias, es decir, puede activar y desactivar diversas implementaciones de la interfaz.

Finalmente, una fábrica se presta a tecnologías de inyección de dependencia como Spring, etc.


3

Los Singletons son útiles cuando se ejecuta un código de lote al inicializar y objetar. Por ejemplo, cuando usa iBatis cuando configura un objeto de persistencia, tiene que leer todas las configuraciones, analizar los mapas, asegurarse de que todo esté correcto, etc. antes de acceder a su código.

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


El patrón prototipo también hace esto, y es más flexible. También puede usarlo cuando su cliente haga muchas instancias de su clase costosa, pero solo un número limitado de ellas tiene un estado diferente. Por ejemplo, tetronimos en Tetris.
Eva

3

La verdadera caída de Singletons es que rompen la herencia. No puede derivar una nueva clase para darle una 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 el Singleton hará que su código esté estrechamente acoplado (reparable mediante un Patrón de estrategia ... también conocido como Inyección de dependencia) también evitará que cierre secciones del código de la revisión (bibliotecas compartidas).

Por lo tanto, incluso los ejemplos de registradores o grupos de subprocesos no son válidos y deben reemplazarse por estrategias.


Los propios madereros no deberían ser solteros. El sistema general de mensajes de "difusión" debería ser. Los propios registradores son suscriptores de los mensajes de difusión.
CashCow

Los grupos de subprocesos tampoco deberían ser singletons. La pregunta general es si alguna vez quieres más de uno de ellos. Si. La última vez que los usé teníamos 3 grupos de subprocesos diferentes en una aplicación.
CashCow

3

La mayoría de las personas usan singletons cuando intentan sentirse bien acerca del uso de una variable global. Hay usos legítimos, pero la mayoría de las veces cuando las personas los usan, el hecho de que solo pueda haber una instancia es solo un hecho trivial en comparación con el hecho de que es accesible a nivel mundial.


3

Debido a que un singleton solo permite que se cree una instancia, controla efectivamente la replicación de la instancia. por ejemplo, no necesitaría múltiples instancias de una búsqueda; por ejemplo, un mapa de búsqueda morse, por lo tanto, envolverlo en una clase singleton es apto. Y el hecho de que tenga una sola instancia de la clase no significa que también esté limitado en el número de referencias a esa instancia. Puede poner en cola las llamadas (para evitar problemas de subprocesos) a la instancia y efectuar los cambios necesarios. Sí, la forma general de un singleton es pública a nivel mundial, ciertamente puede modificar el diseño para crear un singleton más restringido de acceso. No he cansado esto antes, pero estoy seguro de que es posible. Y para todos los que comentaron que el patrón singleton es completamente malo, deben saber esto:



2

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

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

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


2
Eso definitivamente no es mejor. 1: No está definiendo la semántica de propiedad mediante el puntero. Nunca debe usar punteros en C ++ a menos que esté preparado para administrarlos. 2: Su uso del bloqueo de doble verificación es anticuado y existen formas modernas mucho mejores de hacerlo. 3: Tus comentarios sobre la destrucción son ingenuos. La recuperación de la memoria no es el punto de uso del destructor sino de la limpieza. Sugerencias para una mejor versión: mira la pregunta. La versión presentada allí ya es mucho mejor.
Martin York

1

Yo 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 son contratados.


45
Las reglas estrictas y rápidas sobre la contratación lo harán perder una gran diversidad de empleados potenciales.
Karl

13
Existe una gran diversidad de idiotas. Eso no significa que deberían ser considerados para la contratación. Si alguien no menciona ningún patrón de diseño, creo que sería preferible a alguien que conozca el singleton, y no otros patrones.
jalf

3
Para el libro de registro, mi respuesta fue irónica. En mi proceso de entrevista real, trato de evaluar si necesitaremos asesorar a alguien en C ++ y qué tan difícil será. Algunos de mis candidatos favoritos son personas que NO conocen C ++ por dentro y por fuera, pero pude tener una gran conversación con ellos al respecto.
Matt Cruikshank

44
Abajo voto. Desde mi experiencia personal, es posible que el programador no pueda nombrar ningún otro patrón que no sea Singleton, pero eso no significa que use Singletons. Personalmente, estaba usando singletons en mi código ANTES de haber oído hablar de ellos (los llamé "globales más inteligentes"; sabía lo que es el global). Cuando supe de ellos, cuando supe de sus pros y sus contras, dejé de usarlos. De repente, las pruebas unitarias se volvieron mucho más interesantes para mí cuando paré ... ¿Eso me convirtió en un peor programador? Pfff ...
Paulius

3
También estoy votando por la pregunta sin sentido "nombrar algunos patrones de diseño". Diseñar consiste en comprender cómo aplicar patrones de diseño, no solo en poder desenrollar sus nombres. Ok, eso puede no justificar un voto negativo, pero esta respuesta es troll-ish.
CashCow

0

Anti-uso:

Un problema importante con el uso excesivo de singleton es que el patrón impide la fácil extensión e intercambio de implementaciones alternativas. El nombre de la clase está codificado donde se usa el singleton.


Votaron en contra por 2 razones: 1. Singleton puede usar internamente instancias polimórficas (por ejemplo, el Logger global usa estrategias polimórficas de orientación) 2. Puede haber typedef para el nombre de singleton, por lo que el código depende fácticamente de typedef.
topright gamedev

Terminé construyendo mi versión de un singleton para ser extensible usando el patrón de plantilla curiosamente recurrente.
Zachary Kraus

0

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 .


3
Puede quitar las partes que no están específicamente relacionadas con Singletons para que el código sea más fácil de leer.
Martin York

Además, su primera versión no es segura para subprocesos debido a la posible reordenación de lectura / escritura. Ver stackoverflow.com/questions/9666/…
Thomas Danecker el

55
Uh ... ¿lenguaje equivocado? La pregunta es, obviamente, etiquetada como C ++ .
DevSolar

0

El patrón Singleton de Meyers funciona bastante bien la mayor parte del tiempo, y en ocasiones lo hace, no necesariamente vale la pena 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 de ahora en adelante) aunque no todos los GAO son singletons.

Los propios registradores no deberían ser únicos, pero los medios para iniciar sesión deberían ser idealmente accesibles a nivel mundial, para desacoplar dónde se genera el mensaje de registro desde dónde o cómo se registra.

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

Con eso 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 que se haga tan groseramente como eso, y claramente en una biblioteca cargada que contiene objetos, es probable que desee algún otro mecanismo para administrar su vida útil. (Póngalos en un objeto que obtenga cuando cargue la biblioteca).

¿En cuanto a cuando uso singletons? Los usé para 2 cosas: una tabla singleton que indica qué bibliotecas se han cargado con dlopen: un controlador de mensajes al que los registradores pueden suscribirse y al que pueden enviar mensajes. Requerido específicamente para manejadores de señal.


0

Todavía no entiendo por qué un singleton tiene que ser global.

Iba a producir un singleton donde escondía una base de datos dentro de la clase como una variable estática constante privada y hacía funciones de clase que utilizaran la base de datos sin exponer la base de datos al usuario.

No veo por qué esta funcionalidad sería mala.


No entiendo por qué piensas que tiene que ser global.
Martin York

De acuerdo con este hilo, todos decían que un singleton tiene que ser global
Zachary Kraus el

1
No. El hilo indica que un singelton tiene un estado global. No es que sea una variable global. La solución que propone tiene un estado global. La solución que propone también está utilizando una variable global; un miembro estático de una clase es un objeto de "Duración de almacenamiento estático", una variable global es un objeto de "Duración de almacenamiento estático". Por lo tanto, los dos son básicamente lo mismo con semánticas / ámbitos ligeramente diferentes.
Martin York

Entonces, ¿una variable estática privada sigue siendo global debido a la "Duración del almacenamiento estático"?
Zachary Kraus

1
Nota: Te perdiste mi bit deliberadamente ninguno declarado. Su diseño de usar un miembro "privado" estático no es malo de la misma manera que un singelton. Porque no introduce el "estado mutable global". Pero tampoco es un singleton. Un singleton es una clase que está diseñada para que solo pueda existir una instancia del objeto. Lo que sugiere es un estado compartido único para todos los objetos de una clase. Concepto diferente
Martin York

0

Los encuentro útiles cuando tengo una clase que encapsula mucha memoria. Por ejemplo, en un juego reciente en el que he estado trabajando, tengo una clase de mapa de influencia que contiene una colección de matrices muy grandes de memoria contigua. Quiero que todo esté asignado al inicio, todo liberado al apagar y definitivamente solo quiero una copia. También tengo que acceder desde muchos lugares. Creo que el patrón singleton es muy útil en este caso.

Estoy seguro de que hay otras soluciones, pero creo que esta es muy útil y fácil de implementar.


0

Si usted fue quien creó el singleton y quien lo usa, no lo haga como singleton (no tiene sentido porque puede controlar la singularidad del objeto sin hacerlo único) pero tiene sentido cuando es desarrollador de un biblioteca y desea proporcionar un solo objeto a sus usuarios (en este caso, usted es quien creó el singleton, pero no es el usuario).

Los singletons son objetos, así que úselos como objetos, muchas personas acceden a los singletons directamente llamando al método que lo devuelve, pero esto es perjudicial porque está haciendo que su código sepa que el objeto es singleton, prefiero usar los singletons como objetos, los paso a través del constructor y los uso como objetos ordinarios, de esa manera, su código no sabe si estos objetos son únicos o no y eso hace que las dependencias sean más claras y ayuda un poco para refactorizar ...


-1

En las aplicaciones de escritorio (lo sé, ¡solo nosotros, los dinosaurios, las escribimos más!) Son esenciales para obtener una configuración global de aplicación relativamente inmutable: el idioma del usuario, la ruta de acceso a los archivos de ayuda, las preferencias del usuario, etc., que de lo contrario tendrían que propagarse en cada clase y cada diálogo .

Editar: ¡por supuesto, estos deben ser de solo lectura!


Pero esto plantea la pregunta; ¿por qué el idioma del usuario y la ruta del archivo de ayuda tienen que ser instancias de método en absoluto ?
DrPizza

2
Tenemos globales para eso. No hay necesidad de hacerlos
solteros

Variables globales: ¿cómo las serializa desde el registro / base de datos? Clase global: ¿cómo se asegura de que solo haya una?
Martin Beckett

@mgb: los serializa leyendo valores del registro / base de datos y almacenándolos en las variables globales (esto probablemente debería hacerse en la parte superior de su función principal). se asegura de que solo haya un objeto de una clase, al crear solo un objeto de clase ... realmente ... es tan difícil 'grep -rn "new \ + global_class_name". ? ¿De Verdad?
Paulius

77
@mgb: ¿Por qué debería asegurarme de que solo hay uno? Solo necesito saber que una instancia siempre representa la configuración actual . pero no hay razón por la que no se me permita tener otros objetos de configuración alrededor del lugar. Quizás uno para "la configuración que el usuario está definiendo actualmente, pero que aún no ha aplicado", por ejemplo. O uno para "la configuración que el usuario guardó antes para que pueda volver a ellos más tarde". O uno para cada una de sus pruebas unitarias.
jalf

-1

Otra implementación

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

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};

3
Eso es realmente horrible Consulte: stackoverflow.com/questions/1008019/c-singleton-design-pattern/… para una mejor inicialización diferida y una destrucción determinista más importante garantizada.
Martin York

Si va a utilizar punteros, Instance()debe devolver un puntero, no una referencia. Dentro de su .cpparchivo, inicializar la instancia a nulo: Singleton* Singleton::instance_ = nullptr;. Y Instance()debe ser implementado como: if (instance_ == nullptr) instance_ = new Singleton(); return instance_;.
Dennis
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.