it-swarm-es.com

¿Cuándo es apropiado Singleton?

Algunos sostienen que Singleton Pattern siempre es un antipatrón. ¿Qué piensas?

67
Fishtoaster

Las dos críticas principales de Singletons se dividen en dos campos por lo que he observado:

  1. Los programadores menos capaces usan y maltratan los singletons, por lo que todo se convierte en un singleton y se ve un código lleno de referencias Class :: get_instance (). En términos generales, solo hay uno o dos recursos (como una conexión de base de datos, por ejemplo) que califican para el uso del patrón Singleton.
  2. Los singletons son esencialmente clases estáticas, que se basan en uno o más métodos y propiedades estáticos. Todas las cosas estáticas presentan problemas reales y tangibles cuando intentas hacer pruebas unitarias porque representan callejones sin salida en tu código que no pueden ser burlados o apagados. Como resultado, cuando prueba una clase que se basa en un Singleton (o cualquier otro método o clase estática) no solo está probando esa clase sino también el método o clase estática.

Como resultado de ambos, un enfoque común es usar crear un objeto contenedor amplio para contener una sola instancia de estas clases y solo el objeto contenedor modifica estos tipos de clases, mientras que a muchas otras clases se les puede otorgar acceso para usar desde El objeto contenedor.

32
Noah Goodrich

Estoy de acuerdo en que es un antipatrón. ¿Por qué? Porque permite que su código mienta sobre sus dependencias, y no puede confiar en que otros programadores no introduzcan el estado mutable en sus singletons previamente inmutables.

Una clase puede tener un constructor que solo toma una cadena, por lo que cree que se instancia de forma aislada y no tiene efectos secundarios. Sin embargo, silenciosamente, se está comunicando con algún tipo de objeto singleton público, disponible globalmente, de modo que cada vez que crea una instancia de la clase, contiene datos diferentes. Este es un gran problema, no solo para los usuarios de su API, sino también para la comprobabilidad del código. Para realizar una prueba adecuada del código de la unidad, debe realizar una microgestión y conocer el estado global en el singleton para obtener resultados de prueba consistentes.

42
Magnus Wolffelt

El patrón Singleton es básicamente una variable global vagamente inicializada. Las variables globales generalmente se consideran malvadas porque permiten una acción espeluznante a una distancia entre partes aparentemente no relacionadas de un programa. Sin embargo, en mi humilde opinión, no hay nada de malo con las variables globales que se establecen una vez, desde un lugar, como parte de la rutina de inicialización de un programa (por ejemplo, leyendo un archivo de configuración o argumentos de línea de comando) y luego se tratan como constantes. Tal uso de variables globales es diferente solo en letra, no en espíritu, de tener una constante nombrada declarada en tiempo de compilación.

Del mismo modo, mi opinión sobre Singletons es que son malos si y solo si se usan para pasar el estado mutable entre partes aparentemente no relacionadas de un programa. Si no contienen un estado mutable, o si el estado mutable que contienen está completamente encapsulado para que los usuarios del objeto no tengan que saberlo ni siquiera en un entorno multiproceso, entonces no hay nada de malo en ellos.

34
dsimcha

¿Por qué la gente lo usa?

He visto bastantes singletons en el mundo PHP. No recuerdo ningún caso de uso en el que encontré que el patrón estuviera justificado. Pero creo que tengo una idea sobre la motivación por la que la gente lo usó.

  1. Single instancia.

    "Utilice una sola instancia de clase C en toda la aplicación".

    Este es un requisito razonable, p. para la "conexión de base de datos predeterminada". No significa que nunca creará una segunda conexión db, solo significa que generalmente trabaja con la conexión predeterminada.

  2. Individual ¡instanciación.

    "No permita que la clase C se instancia más de una vez (por proceso, por solicitud, etc.)".

    Esto solo es relevante si instanciar la clase tendría efectos secundarios que entran en conflicto con otras instancias.

    A menudo, estos conflictos se pueden evitar rediseñando el componente, p. eliminando los efectos secundarios del constructor de la clase. O pueden resolverse de otras maneras. Pero aún podría haber algunos casos de uso legítimos.

    También debe pensar si el requisito de "solo uno" realmente significa "uno por proceso". P.ej. para la concurrencia de recursos, el requisito es más bien "uno por sistema completo, entre procesos" y no "uno por proceso". Y para otras cosas es más bien por "contexto de aplicación", y resulta que tiene un contexto de aplicación por proceso.

    De lo contrario, no hay necesidad de hacer cumplir esta suposición. Hacer cumplir esto también significa que no puede crear una instancia separada para las pruebas unitarias.

  3. Acceso global.

    Esto es legítimo solo si no tiene una infraestructura adecuada para pasar objetos al lugar donde se usan. Esto podría significar que su marco o entorno apesta, pero es posible que no esté dentro de sus poderes arreglar eso.

    El precio es un acoplamiento estrecho, dependencias ocultas y todo lo malo del estado global. Pero probablemente ya estés sufriendo esto.

  4. Instanciación perezosa.

    Esta no es una parte necesaria de un singleton, pero parece la forma más popular de implementarlos. Pero, si bien la instanciación perezosa es algo agradable, realmente no necesita un singleton para lograrlo.

Implementación típica

La implementación típica es una clase con un constructor privado, y una variable de instancia estática, y un método getInstance () estático con instanciación diferida.

Además de los problemas mencionados anteriormente, esto muerde con el principio de responsabilidad única , porque la clase sí controla su propia instanciación y ciclo de vida , además de las otras responsabilidades que el clase ya tiene.

Conclusión

En muchos casos, puede lograr el mismo resultado sin un singleton y sin un estado global. En su lugar, debe usar la inyección de dependencia, y es posible que desee considerar un contenedor de inyección de dependencia .

Sin embargo, hay casos de uso en los que le quedan los siguientes requisitos válidos:

  • Instancia única (pero no instanciación única)
  • Acceso global (porque está trabajando con un marco de la edad de bronce)
  • Instanciación perezosa (porque es agradable tenerla)

Entonces, esto es lo que puede hacer en este caso:

  • Cree una clase C que desee instanciar, con un constructor público.

  • Cree una clase S separada con una variable de instancia estática y un método S :: getInstance () estático con instanciación diferida, que usará la clase C para la instancia.

  • Elimine todos los efectos secundarios del constructor de C. En su lugar, coloque estos efectos secundarios en el método S :: getInstance ().

  • Si tiene más de una clase con los requisitos anteriores, puede considerar administrar las instancias de clase con un pequeño contenedor de servicio local , y usar la instancia estática solo para el contenedor. Entonces, S :: getContainer () le dará un contenedor de servicio con instancia diferida, y usted obtendrá los otros objetos del contenedor.

  • Evite llamar al getInstance () estático donde pueda. Utilice la inyección de dependencia en su lugar, siempre que sea posible. Especialmente, si utiliza el enfoque de contenedor con varios objetos que dependen unos de otros, entonces ninguno de estos debería tener que llamar a S :: getContainer ().

  • Opcionalmente, cree una interfaz que implemente la clase C, y úsela para documentar el valor de retorno de S :: getInstance ().

(¿Todavía llamamos a esto un singleton? Dejo esto en la sección de comentarios ...)

Beneficios:

  • Puede crear una instancia separada de C para pruebas unitarias, sin tocar ningún estado global.

  • La gestión de instancias está separada de la clase misma -> separación de preocupaciones, principio de responsabilidad única.

  • Sería bastante fácil dejar que S :: getInstance () use una clase diferente para la instancia, o incluso determinar dinámicamente qué clase usar.

7
donquixote

Personalmente, usaré singletons cuando necesite 1, 2 o 3, o una cantidad limitada de los objetos para la clase particular en cuestión. O quiero transmitir al usuario de mi clase que no quiero que se creen varias instancias de mi clase para que funcione correctamente.

Además, solo lo usaré cuando necesite usarlo en casi todas partes de mi código y no quiera pasar un objeto como parámetro a cada clase o función que lo necesite.

Además, solo usaré un singleton si no rompe la transparencia referencial de otra función. Es decir, dada alguna entrada, siempre producirá la misma salida. Es decir. No lo uso para el estado global. A menos que posiblemente ese estado global se inicialice una vez y nunca cambie.

En cuanto a cuándo no usarlo, vea los 3 anteriores y cámbielos a lo opuesto.

1
Brian R. Bondy