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

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

Dame escenarios, que no sean el buen viejo registrador, donde tiene sentido usar el singleton.

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 los internets 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 necesita ser utilizada una y otra vez hasta el cansancio por cada clase en un proyecto. Si cada clase utiliza esta clase de registro, la dependency injection se vuelve engorrosa.

El registro es un ejemplo específico de Singleton “aceptable” porque no afecta la ejecución de su código. Desactivar el registro, la ejecución del código sigue siendo la misma. Habilítalo, lo mismo. Misko lo explica de la siguiente manera en Root Cause of Singletons : “La información aquí fluye en una dirección: desde su aplicación al registrador. Aunque los registradores son de estado global, dado que no fluye información de los registradores a su aplicación, los registradores son aceptables”.

Estoy seguro de que hay otras razones válidas también. Alex Miller, en ” Patterns I Hate “, habla de los localizadores de servicios y las IU del lado del cliente que también son opciones posiblemente “aceptables”.

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

Un candidato de Singleton debe cumplir tres requisitos:

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

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

Por ejemplo, es poco probable que una cola de impresión sea llamada 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.

Lectura de archivos de configuración que solo deben leerse en el momento del inicio y encapsularlos en un Singleton.

Utiliza un singleton cuando necesita administrar un recurso compartido. Por ejemplo, una cola de impresión. Su aplicación solo debe tener una instancia única 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.

Los archivos únicos de lectura que almacenan algún estado global (lenguaje de usuario, ruta de archivo de ayuda, ruta de la aplicación) son razonables. Tenga cuidado con el uso de singletons para controlar la lógica comercial: el single casi siempre termina siendo múltiple

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 en archivos de configuración externos.

Una de las formas en que utiliza un singleton es cubrir una instancia en la que debe haber un único “intermediario” que controle el acceso a un recurso. Los singletons son buenos en los madereros porque intermedian el acceso a, digamos, un archivo, que solo puede escribirse exclusivamente. Para algo como el registro, proporcionan una forma de abstraer las escrituras a algo así como un archivo de registro: puede envolver un mecanismo de almacenamiento en caché en su singleton, etc.

También piense en una situación en la que tenga una aplicación con muchas ventanas / hilos / etc., pero que necesite un único punto de comunicación. Una vez usé uno para controlar trabajos que quería que mi aplicación lanzara. El singleton era responsable de serializar los trabajos y mostrar su estado a cualquier otra parte del progtwig que estuviera interesada. 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

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

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

Un ejemplo práctico de singleton se puede encontrar en Test :: Builder , la clase que respalda casi todos los módulos modernos de pruebas de Perl. The Test :: Builder Singleton almacena e intermediarios el estado y el historial del proceso de prueba (resultados de pruebas históricas, cuenta el número de pruebas que se ejecutan) y cosas como hacia dónde se dirige el resultado 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 guión de prueba.

La historia del singleton de Test :: Builder es educativa. Llamar a new() siempre le proporciona el mismo objeto. En primer lugar, todos los datos se almacenaron como variables de clase con nada en el objeto en sí. Esto funcionó hasta que quise probar Test :: Builder consigo mismo. Luego necesité dos objetos Test :: Builder, uno configurado como ficticio, para capturar y probar su comportamiento y resultados, y uno para ser el verdadero objeto de prueba. En ese momento, Test :: Builder se refactivó en un objeto real. El objeto singleton se almacenó como datos de clase y new() siempre lo devolverá. Se agregó create() para crear un objeto nuevo y habilitar las pruebas.

Actualmente, los usuarios desean cambiar algunos comportamientos de Test :: Builder en su propio módulo, pero dejan a otros solos, 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 monolítico Test :: Builder se está dividiendo en partes más pequeñas (historial, salida, formato …) con una instancia de Test :: Builder que los reúne. Ahora Test :: Builder ya no tiene que ser un singleton. Sus componentes, como la historia, pueden serlo. Esto empuja la inflexible necesidad de un singleton a un nivel inferior. 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 los objetos que los contienen pueden decidir cómo usarlos. Incluso permite el uso de una clase que no sea Test :: Builder utilizando el historial Test :: Builder y los singletons de salida.

Parece que hay un empuje y un tirón entre la coordinación de los datos y la flexibilidad del comportamiento que se puede mitigar al poner el singleton solo en los datos compartidos con la menor cantidad de comportamiento posible para garantizar la integridad de los datos.

Creo que el uso de singleton se puede considerar como lo mismo que la 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, es allí donde tiene sentido usar singletons.

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 Lucene Search Index Writer.

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

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

Es un uso real de objetos -> la clase de plantilla contiene la plantilla de página que se está construyendo, y los módulos que agregan al resultado de la página le dan forma, la agregan y la modifican. Debe mantenerse como una sola instancia para que esto pueda suceder, y lo mismo ocurre con las bases de datos. Con un singleton de base de datos compartida, todas las clases de los módulos pueden obtener acceso a las consultas y obtenerlas sin tener que volver a ejecutarlas.

Un singleton de depósito de variable global le proporciona un almacén de variables global, confiable y de fácil uso. Arregla tu código mucho. 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 para la página, obtienes, por ejemplo, un singleton $template maduro, un singleton de $gb que tiene la matriz de lang para reemplazarlo, y todos los resultados cargados y listos. Simplemente los reemplaza en las claves que ahora están presentes en el valor de la página del objeto de la plantilla madura, y luego lo muestra al usuario.

La gran ventaja de esto es que puedes hacer CUALQUIER postprocesamiento que te guste 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.

Puede usar Singleton al implementar el patrón de estado (de la manera que se muestra en el libro de 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 singleton.

Puede ser muy pragmático configurar inquietudes de infraestructura específicas 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 para el marco.

En este caso, está tomando una dependencia de la infraestructura para simplificar el uso de la biblioteca y evitar la complejidad innecesaria.

Lo uso para un objeto que encapsula parámetros de línea de comandos cuando se trata de módulos conectables. El progtwig principal no sabe cuáles son los parámetros de línea de comandos para los módulos que se cargan (y ni siquiera sabe qué módulos se están cargando). por ejemplo, cargas principales A, que no necesita ningún parámetro en sí mismo (entonces, ¿por qué debería tomar un puntero / referencia extra / 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 al singleton de línea de comandos para indicarle qué parámetros aceptar, y en tiempo de ejecución vuelven a llamar para averiguar si el usuario realmente ha especificado alguna de ellos.

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

Un ejemplo con código, tal vez.

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

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

Ed.

1 – Un comentario en la primera respuesta:

No estoy de acuerdo con una clase estática Logger. esto puede ser práctico para una implementación, pero no puede ser reemplazado para pruebas unitarias. Una clase estática no puede ser reemplazada por una prueba doble. Si no prueba la unidad, 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 necesitaba un singleton, usaría un marco de dependency injection (Spring.NET, Unity para .NET, Spring para Java) u otro.