Sobre patrones de diseño: ¿Cuándo debo usar el singleton?

448

La variable global glorificada se convierte en una clase global glorificada. Algunos dicen que rompe el diseño orientado a objetos.

Dame escenarios, además del buen viejo registrador donde tiene sentido usar el singleton.

Setori
fuente
44
Desde que aprendí erlang, prefiero ese enfoque, es decir, la inmutabilidad y la transmisión de mensajes.
Setori el
209
¿Qué no es constructivo sobre esta pregunta? Veo una respuesta constructiva a continuación.
mk12
3
Un marco de inyección de dependencias es un singleton muy complejo que da un objeto ...
Ian Ringrose
1
Singleton se puede usar como un objeto administrador entre las instancias de otros objetos, por lo tanto, solo debe haber una instancia del singleton donde las otras instancias se comuniquen a través de la instancia de singleton.
Levent Divilioglu
Tengo una pregunta secundaria: cualquier implementación de Singleton también se puede implementar utilizando una clase "estática" (con un método "factory" / "init"), sin crear realmente una instancia de una clase (se podría decir que una clase estática es un tipo de implementación de Singleton, pero ...): ¿por qué debería uno usar un Singleton real (una instancia de clase única que se asegura de que sea única) en lugar de una clase estática? La única razón por la que puedo pensar es quizás por la "semántica", pero incluso en ese sentido, los casos de uso de Singleton no requieren una relación "clase-> instancia" por definición ... así que ... ¿por qué?
Yuval A.

Respuestas:

358

En mi búsqueda de la verdad descubrí que en realidad hay muy pocas razones "aceptables" para usar un Singleton.

Una razón que tiende a aparecer una y otra vez en Internet es la de una clase de "registro" (que usted mencionó). En este caso, se puede usar un Singleton en lugar de una sola instancia de una clase porque una clase de registro generalmente debe usarse una y otra vez hasta el cansancio por cada clase en un proyecto. Si cada clase usa esta clase de registro, la inyección de dependencia se vuelve engorrosa.

El registro es un ejemplo específico de un Singleton "aceptable" porque no afecta la ejecución de su código. Deshabilite el registro, la ejecución del código sigue siendo la misma. Habilítelo, lo mismo. Misko lo expresa de la siguiente manera en Causa raíz de Singletons : "La información aquí fluye de una manera: desde su aplicación al registrador. Aunque los registradores son de estado global, ya que no fluye información de los registradores a su aplicación, los registradores son aceptables".

Estoy seguro de que también hay otras razones válidas. Alex Miller, en " Patrones que odio ", habla de que los localizadores de servicios y la interfaz de usuario del lado del cliente también son opciones posiblemente "aceptables".

Lee más en Singleton Te amo, pero me estás deprimiendo.

CodificaciónSin Comentarios
fuente
3
@ArneMertz Supongo que este es el indicado .
Atacante el
1
¿Por qué no puedes usar un objeto global? ¿Por qué tiene que ser un singleton?
Zapato
1
Creo que el método estático para una utilidad de registro?
Skynet
1
Los Singletons son mejores cuando necesita administrar recursos. Por ejemplo, conexiones HTTP. No desea establecer 1 millón de clientes http para un solo cliente, eso es un desperdicio loco y lento. Por lo tanto, un singleton con un cliente http agrupado de conexión será mucho más rápido y amigable con los recursos.
Cogman
3
Sé que esta es una vieja pregunta, y la información en esta respuesta es excelente. Sin embargo, tengo problemas para entender por qué esta es la respuesta aceptada cuando el OP especificó claramente: "Dame escenarios, aparte del buen registrador antiguo en el que tiene sentido usar el singleton".
Francisco C.
124

Un candidato Singleton debe cumplir tres requisitos:

  • controla el acceso concurrente a un recurso compartido.
  • Se solicitará acceso al recurso desde múltiples partes dispares del sistema.
  • solo puede haber un objeto.

Si su Singleton propuesto tiene solo uno o dos de estos requisitos, un rediseño es casi siempre la opción correcta.

Por ejemplo, es poco probable que se llame a una cola de impresión desde más de un lugar (el menú Imprimir), por lo que puede usar mutexes para resolver el problema de acceso concurrente.

Un registrador simple es el ejemplo más obvio de un Singleton posiblemente válido, pero esto puede cambiar con esquemas de registro más complejos.

metao
fuente
3
No estoy de acuerdo con el punto 2. El punto 3 no es realmente una razón (solo porque puedas, no significa que debas hacerlo) y el 1 es un buen punto, pero todavía no creo que sea útil. Digamos que el recurso compartido es una unidad de disco o un caché de base de datos. Puede agregar otra unidad o tener una memoria caché de base de datos que se centre en otra cosa (como una memoria caché para una tabla especializada para un subproceso con el otro como un propósito más general).
15
Creo que te perdiste la palabra "candidato". Un candidato Singleton debe cumplir los tres requisitos; el hecho de que algo cumpla con los requisitos no significa que deba ser un Singleton. Puede haber otros factores de diseño :)
metao
45

Lectura de archivos de configuración que solo deberían leerse en el momento del inicio y encapsulados en un Singleton

Paul Croarkin
fuente
8
Similar a Properties.Settings.Defaulten .NET.
Nick Bedford
99
@Paul, el "campo no-singleton" indicará que el objeto de configuración simplemente debe pasarse a las funciones que lo necesitan, en lugar de hacerlo accesible globalmente (también conocido como singleton).
Pacerier
2
Discrepar. Si la configuración se traslada a la base de datos, todo está atornillado. Si la ruta a la configuración depende de algo fuera de ese singleton, estas cosas también deben ser estáticas.
rr-
3
@PaulCroarkin ¿Puedes ampliar esto y explicar cómo es beneficioso?
AlexG
1
@ rr: si la configuración se traslada a la base de datos, aún se puede encapsular en un objeto de configuración que se pasará a las funciones que lo necesiten. (PD: no estoy en el campo "no-singleton").
Will Sheppard
36

Utiliza un singleton cuando necesita administrar un recurso compartido. Por ejemplo, una cola de impresión. Su aplicación solo debe tener una única instancia de la cola de impresión para evitar solicitudes conflictivas para el mismo recurso.

O una conexión de base de datos o un administrador de archivos, etc.

Vincent Ramdhanie
fuente
30
He escuchado este ejemplo de cola de impresión y creo que es un poco lamentable. ¿Quién dice que no puedo tener más de una cola de impresión? ¿Qué diablos es una cola de impresión de todos modos? ¿Qué sucede si tengo diferentes tipos de impresoras que no pueden entrar en conflicto o usar diferentes controladores?
1800 INFORMACIÓN
66
Es solo un ejemplo ... para cualquier situación que alguien use como ejemplo, podrá encontrar un diseño alternativo que haga que el ejemplo sea inútil. Supongamos que la cola de impresión gestiona un único recurso compartido por múltiples componentes. Funciona.
Vincent Ramdhanie
2
Es el ejemplo clásico de la Banda de los Cuatro. Creo que una respuesta con una verdadera probado caso de uso sería más útil. Me refiero a una situación en la que realmente sentiste que Singleton es la mejor solución.
Andrei Vajna II
Un recurso compartido es, en mi opinión, un ejemplo demasiado amplio. ¿Cómo probaría que los objetos que utilizan la cola de impresión funcionan correctamente frente a una cola de impresión defectuosa cuando no puede inyectar una implementación de "cola de impresión" que no funciona correctamente? aunque breve y poco informativa, la respuesta aceptada es un enfoque mucho más seguro en mi libro
Rune FS
¿Qué diablos es una cola de impresión?
RayLoveless
23

Los singletons de solo lectura que almacenan algunos estados globales (lenguaje de usuario, ruta de ayuda, ruta de aplicación) son razonables. Tenga cuidado de usar singletons para controlar la lógica de negocios: el single casi siempre termina siendo múltiple

Martin Beckett
fuente
44
El lenguaje del usuario solo puede ser singleton con la suposición de que solo un usuario puede usar el sistema.
Samuel Åslund
... y ese usuario solo habla un idioma.
espectros
17

Administrar una conexión (o un grupo de conexiones) a una base de datos.

Lo usaría también para recuperar y almacenar información sobre archivos de configuración externos.

Federico A. Ramponi
fuente
2
¿No sería un generador de conexión de base de datos un ejemplo de una Fábrica?
Ken
3
@ Ken, querrás que esa fábrica sea un singleton en casi todos los casos.
Chris Marisic
2
@Federico, el "campo no-singleton" indicará que estas conexiones de base de datos simplemente deben pasarse a funciones que las necesitan, en lugar de hacerlas accesibles globalmente (también conocido como singleton).
Pacerier
3
Realmente no necesitas un singleton para esto. Se puede inyectar.
Nestor Ledon
11

Una de las formas en que utiliza un singleton es para cubrir una instancia en la que debe haber un único "intermediario" que controle el acceso a un recurso. Los Singletons son buenos en los registradores porque brindan acceso a, digamos, un archivo, en el que solo se puede escribir exclusivamente. Para algo como el registro, proporcionan una forma de abstraer las escrituras a algo así como un archivo de registro: puede ajustar un mecanismo de almacenamiento en caché a su singleton, etc.

Piense también en una situación en la que tiene una aplicación con muchas ventanas / hilos / etc, pero que necesita un único punto de comunicación. Una vez usé uno para controlar los trabajos que quería que mi aplicación se iniciara. El singleton fue responsable de serializar los trabajos y mostrar su estado a cualquier otra parte del programa que estuviera interesado. En este tipo de escenario, puede ver un singleton como algo así como una clase de "servidor" que se ejecuta dentro de su aplicación ... HTH

Dave Markle
fuente
3
Los registradores suelen ser Singletons para que los objetos de registro no tengan que pasarse. Cualquier implementación decente de una secuencia de registro garantizará que las escrituras concurrentes sean imposibles, ya sea Singleton o no.
metao
10

Se debe usar un singleton al administrar el acceso a un recurso compartido por toda la aplicación, y sería destructivo tener múltiples instancias de la misma clase. Asegurarse de que el acceso a recursos compartidos sea seguro para subprocesos es un muy buen ejemplo de dónde este tipo de patrón puede ser vital.

Cuando use Singletons, debe asegurarse de no ocultar accidentalmente dependencias. Idealmente, los singletons (como la mayoría de las variables estáticas en una aplicación) se deben configurar durante la ejecución de su código de inicialización para la aplicación (static void Main () para ejecutables de C #, static void main () para ejecutables de java) y luego pasarlos a todas las demás clases que se crean instancias que lo requieren. Esto le ayuda a mantener la capacidad de prueba.

Adam Ness
fuente
8

Creo que el uso único puede considerarse como la misma relación de muchos a uno en las bases de datos. Si tiene muchas partes diferentes de su código que necesitan trabajar con una sola instancia de un objeto, ahí es donde tiene sentido usar singletons.

daalbert
fuente
6

Un ejemplo práctico de un singleton se puede encontrar en Test :: Builder , la clase que respalda casi todos los módulos de prueba modernos de Perl. Test :: Builder Singleton almacena y corre el estado y el historial del proceso de prueba (resultados históricos de la prueba, cuenta el número de pruebas ejecutadas), así como cosas como a dónde va la salida de la prueba. Todos estos son necesarios para coordinar múltiples módulos de prueba, escritos por diferentes autores, para trabajar juntos en un solo script de prueba.

La historia de Test :: Builder's singleton es educativa. Llamar new()siempre te da el mismo objeto. Primero, todos los datos se almacenaron como variables de clase sin nada en el objeto mismo. Esto funcionó hasta que quería probar Test :: Builder consigo mismo. Luego, necesitaba dos objetos Test :: Builder, uno configurado como un ficticio, para capturar y probar su comportamiento y salida, y uno para ser el objeto de prueba real. En ese momento, Test :: Builder se refactorizó en un objeto real. El objeto singleton se almacenó como datos de clase y new()siempre lo devolvería. create()fue agregado para hacer un objeto nuevo y permitir la prueba.

Actualmente, los usuarios desean cambiar algunos comportamientos de Test :: Builder en su propio módulo, pero dejan a otros en paz, mientras que el historial de pruebas permanece en común en todos los módulos de prueba. Lo que está sucediendo ahora es que el objeto Test :: Builder monolítico se está dividiendo en partes más pequeñas (historial, salida, formato ...) con una instancia de Test :: Builder reuniéndolos juntos. Ahora Test :: Builder ya no tiene que ser un singleton. Sus componentes, como la historia, pueden ser. Esto empuja la necesidad inflexible de un singleton por un nivel. Le da más flexibilidad al usuario para mezclar y combinar piezas. Los objetos singleton más pequeños ahora solo pueden almacenar datos, y sus objetos que contienen deciden cómo usarlos. Incluso permite que una clase que no sea Test :: Builder juegue utilizando el historial Test :: Builder y los singletons de salida.

Parece que hay un empuje y atracción entre la coordinación de datos y la flexibilidad de comportamiento que puede mitigarse colocando el singleton alrededor de los datos compartidos con la menor cantidad de comportamiento posible para garantizar la integridad de los datos.

Schwern
fuente
5

Cuando carga un objeto Propiedades de configuración, ya sea desde la base de datos o un archivo, ayuda tenerlo como un singleton; no hay razón para seguir leyendo datos estáticos que no cambiarán mientras el servidor se esté ejecutando.

Dean J
fuente
2
¿Por qué no cargaría los datos una vez y pasaría el objeto de configuración según sea necesario?
lagweezle
¿Qué pasa con el paso? Si tuviera que pasar cada objeto que necesito, tendría constructores con 20 argumentos ...
Enerccio
@Enerccio Si tiene objetos que dependen de otros 20 diferentes sin encapsulación, ya tiene problemas importantes de diseño.
espectros
@spectras Do I? Si implemento el diálogo gui, necesitaré: repositorio, localización, datos de sesión, datos de la aplicación, widget principal, datos del cliente, administrador de permisos y probablemente más. Claro, puedes agregar algunos, pero ¿por qué? Personalmente, uso spring y aspectos para conectar automáticamente todas estas dependencias a la clase de widget y eso desacopla todo.
Enerccio
Si tiene tanto estado, podría considerar implementar una fachada, dando una visión de los aspectos relevantes para el contexto específico. ¿Por qué? Porque permitiría un diseño limpio sin ninguno de los antipatrones de constructor singleton o 29-arg. En realidad, el hecho mismo de que su diálogo GUI acceda a todas esas cosas grita "violación del principio de responsabilidad única".
espectras
3

Como todos han dicho, un recurso compartido, específicamente algo que no puede manejar el acceso concurrente.

Un ejemplo específico que he visto es un escritor de índice de búsqueda de Lucene.

Miguel
fuente
1
Y sin embargo, IndexWriter no es un singleton ...
Mark
3

Puede usar Singleton al implementar el patrón de estado (de la manera que se muestra en el libro GoF). Esto se debe a que las clases de estado concretas no tienen un estado propio y realizan sus acciones en términos de una clase de contexto.

También puede hacer que Abstract Factory sea un singleton.

Emile Cormier
fuente
Este es el caso con el que estoy tratando ahora en un proyecto. Usé un patrón de estado para eliminar el código condicional repetitivo de los métodos del contexto. Los estados no tienen variables de instancia propias. Sin embargo, estoy en la cerca con respecto a si debo hacerlos solteros. Cada vez que el estado cambia, se instancia una nueva instancia. Esto parece un desperdicio porque no hay forma de que la instancia pueda ser diferente de otra, (porque no hay variables de instancia). Estoy tratando de entender por qué no debería usarlo.
kiwicomb123
1
@ kiwicomb123 Intenta setState()responsabilizarte para decidir la política de creación del estado. Ayuda si su lenguaje de programación admite plantillas o genéricos. En lugar de Singleton, podría usar el patrón Monostate , donde instanciar un objeto de estado termina reutilizando el mismo objeto de estado global / estático. La sintaxis para cambiar el estado podría permanecer sin cambios, ya que sus usuarios no necesitan saber que el estado instanciado es un Monostate.
Emile Cormier el
De acuerdo, entonces, en mis estados, podría hacer que todos los métodos sean estáticos, así que cada vez que se crea una nueva instancia, ¿no tiene la misma sobrecarga? Estoy un poco confundido, necesito leer sobre el patrón Monostate.
kiwicomb123
@ kiwicomb123 No, Monostate no se trata de hacer que todos los miembros estén estáticos. Mejor que lo lea, luego verifique SO para preguntas y respuestas relacionadas.
Emile Cormier
Siento que esto debería tener más votos. La fábrica abstracta es bastante común y dado que las fábricas no tienen estado, son estables en estado y no se pueden implementar con métodos estáticos (en Java) que no se anulan, el uso de singleton debería estar bien.
DPM
3

Recursos compartidos. Especialmente en PHP, una clase de base de datos, una clase de plantilla y una clase de depósito de variable global. Todos tienen que ser compartidos por todos los módulos / clases que se utilizan en todo el código.

Es un verdadero uso de objetos:> la clase de plantilla contiene la plantilla de página que se está creando, y los módulos que se agregan a la salida de la página le dan forma, agregan y cambian. Debe mantenerse como una única instancia para que esto pueda suceder, y lo mismo ocurre con las bases de datos. Con una base de datos compartida única, todas las clases de los módulos pueden acceder a las consultas y obtenerlas sin tener que volver a ejecutarlas.

Un depósito variable global singleton le proporciona un depósito variable global, confiable y fácil de usar. Limpia mucho tu código. Imagine tener todos los valores de configuración en una matriz en un singleton como:

$gb->config['hostname']

o tener todos los valores de idioma en una matriz como:

$gb->lang['ENTER_USER']

Al final de ejecutar el código de la página, obtienes, por ejemplo, un ahora maduro:

$template

Singleton, un $gbsingleton que tiene la matriz lang para reemplazarlo, y toda la salida está cargada y lista. Simplemente reemplácelos en las claves que ahora están presentes en el valor de página del objeto de plantilla maduro y luego se lo entrega al usuario.

La gran ventaja de esto es que puede hacer CUALQUIER procesamiento posterior que desee en cualquier cosa. Puede canalizar todos los valores de idioma a google translate u otro servicio de traducción y recuperarlos, y reemplazarlos en sus lugares, traducidos, por ejemplo. o, puede reemplazar en estructuras de página, o cadenas de contenido, como desee.

Ozgur Zeren
fuente
21
Es posible que desee dividir su respuesta en varios párrafos y bloquear los segmentos de código para facilitar su lectura.
Justin
1

Puede ser muy pragmático configurar problemas específicos de infraestructura como singletons o variables globales. Mi ejemplo favorito de esto son los marcos de inyección de dependencia que hacen uso de singletons para actuar como un punto de conexión al marco.

En este caso, dependerá de la infraestructura para simplificar el uso de la biblioteca y evitar la complejidad innecesaria.

smaclell
fuente
0

Lo uso para un objeto que encapsula los parámetros de la línea de comandos cuando se trata de módulos conectables. El programa principal no sabe cuáles son los parámetros de la línea de comandos para los módulos que se cargan (y ni siquiera sabe qué módulos se están cargando). p. ej., las cargas principales A, que no necesitan ningún parámetro en sí (entonces, por qué debería tomar un puntero / referencia adicional / lo que sea, no estoy seguro, parece contaminación), luego carga los módulos X, Y y Z. Dos de estos, digamos X y Z, necesitan (o aceptan) parámetros, por lo que vuelven a llamar a la línea de comando singleton para decirle qué parámetros aceptar, y en el tiempo de ejecución que llaman para averiguar si el usuario realmente ha especificado de ellos.

En muchos sentidos, un singleton para manejar parámetros CGI funcionaría de manera similar si solo usa un proceso por consulta (otros métodos mod_ * no hacen esto, por lo que sería malo allí, por lo tanto, el argumento que dice que no debería ' No use singletons en el mundo mod_cgi en caso de que se transfiera al mod_perl o cualquier otro mundo).

Tanktalus
fuente
-1

Un ejemplo con código, tal vez.

Aquí, ConcreteRegistry es un singleton en un juego de póker que permite que los comportamientos hasta el árbol del paquete accedan a las pocas interfaces principales del juego (es decir, las fachadas para el modelo, vista, controlador, entorno, etc.):

http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html

Ed.


fuente
1
El enlace ahora está roto, pero si está registrando la información de visualización en un singleton, al que se accederá a través de la aplicación, se está perdiendo el punto de MVC. Una vista se actualiza (y se comunica con) un controlador, que utiliza el modelo. Como parece aquí, es probable que sea un mal uso de Singleton y una refactorización está en orden.
drharris
-9

1 - Un comentario sobre la primera respuesta:

No estoy de acuerdo con una clase de registrador estático. Esto puede ser práctico para una implementación, pero no puede ser reemplazable para pruebas unitarias. Una clase estática no puede ser reemplazada por una prueba doble. Si no realiza una prueba unitaria, no verá el problema aquí.

2 - Intento no crear un singleton a mano. Simplemente creo un objeto simple con constructores que me permiten inyectar colaboradores en el objeto. Si necesitara un singleton, usaría un marco de inyección de dependencia (Spring.NET, Unity para .NET, Spring para Java), o algún otro.

bloparod
fuente
11
Debe comentar las respuestas directamente haciendo clic en el enlace en la parte inferior de una respuesta; Es mucho más fácil leer de esa manera. Además, la respuesta que vio en la parte superior probablemente no sea la primera. Las respuestas se reordenan todo el tiempo.
Ross
¿por qué querría el registro de prueba unitaria?
Enerccio
Hay una gran diferencia entre "una clase de registrador estático" y una instancia de registrador estático . El patrón singleton no dice "haga que su clase sea estática", dice que haga que el acceso a la instancia de un objeto sea estático. Entonces, por ejemplo, ILogger logger = Logger.SingleInstance();cuando este método es estático y devuelve una instancia estáticamente almacenada de un ILogger. Usó el ejemplo de "un marco de inyección de dependencias". Casi todos los contenedores DI son singletons; sus configuraciones se definen de forma estática y, en última instancia, se accede a ellas desde / almacenadas en una única interfaz de proveedor de servicios.
Jon Davis