language agnostic - Singletons: ¿buen diseño o una muleta?
language-agnostic design-patterns (23)
No se trataba solo de un conjunto de variables en un disfraz porque esto tenía docenas de responsabilidades, como comunicarse con la capa de persistencia para guardar / recuperar datos sobre la empresa, tratar con los empleados y las colecciones de precios, etc.
Debo decir que realmente no está describiendo algo que debería ser un solo objeto y es discutible que cualquiera de ellos, aparte de la serialización de datos, debería haber sido un singelton.
Puedo ver al menos 3 conjuntos de clases en las que normalmente diseñaría, pero tiendo a favorecer a objetos más pequeños y más simples que hacen un conjunto limitado de tareas muy bien. Sé que esta no es la naturaleza de la mayoría de los programadores. (Sí, trabajo en 5000 monstruosidades de clase de línea todos los días, y tengo un amor especial por los métodos de 1200 líneas que algunas personas escriben).
Creo que el punto es que en la mayoría de los casos no necesitas un singelton y muchas veces solo haces tu vida más difícil.
Los Singleton son un patrón de diseño muy debatido, por lo que estoy interesado en lo que la comunidad de Stack Overflow pensó sobre ellos.
Proporcione los motivos de sus opiniones, no solo "¡Los Singleton son para programadores perezosos!"
Aquí hay un artículo bastante bueno sobre el tema, aunque está en contra del uso de Singletons: scientificninja.com: performant-singletons .
¿Alguien tiene otros buenos artículos sobre ellos? Tal vez en apoyo de Singletons?
¡Guerras santas! Ok, déjame ver ... La última vez que revisé el diseño, la policía dijo ...
Los singletons son malos porque dificultan las pruebas automáticas: las instancias no se pueden crear de nuevo para cada caso de prueba. En cambio, la lógica debe estar en una clase (A) que pueda ser fácilmente instanciada y probada. Otra clase (B) debería ser responsable de restringir la creación. Principio de Responsabilidad Individual en primer plano! Debería ser conocimiento del equipo que se supone que debes pasar por B para acceder a A, una especie de convención de equipo.
Estoy de acuerdo en su mayoría ..
Creo que hay un gran malentendido sobre el uso del patrón de Singleton. La mayoría de los comentarios aquí se refieren a él como un lugar para acceder a datos globales. Tenemos que tener cuidado aquí: Singleton como patrón no es para acceder a los globales.
Singleton debe usarse para tener solo una instancia de la clase dada. El repositorio de patrones tiene gran información sobre Singleton .
El mayor problema con los singleton es que dificultan las pruebas unitarias, especialmente cuando desea ejecutar sus pruebas en paralelo pero de forma independiente.
El segundo es que las personas a menudo creen que la inicialización lenta con bloqueo comprobado es una buena forma de implementarlas.
Finalmente, a menos que sus singletons sean inmutables, entonces pueden convertirse fácilmente en un problema de rendimiento cuando intente escalar su aplicación para ejecutarla en múltiples hilos en múltiples procesadores. La sincronización establecida es costosa en la mayoría de los entornos.
El objetivo de Singleton es garantizar que una clase tenga solo una instancia y proporcione un punto de acceso global a ella. La mayoría de las veces, el foco está en el punto de instancia única. Imagínese si se llamara Globalton. Sería menos atractivo ya que esto enfatiza las connotaciones (generalmente) negativas de una variable global.
La mayoría de los buenos argumentos en contra de los singleton tienen que ver con la dificultad que presentan en las pruebas, ya que crear dobles de prueba para ellos no es fácil.
En defensa de singletons:
- No son tan malos como los globales porque los globales no tienen un orden de inicialización forzado por el estándar, y usted puede ver fácilmente errores no deterministas debido a órdenes de dependencia ingenuas o inesperadas. Los Singletons (suponiendo que estén asignados en el montón) se crean después de todos los globales, y en un lugar muy predecible en el código.
- Son muy útiles para sistemas de almacenamiento / carencia de recursos tales como una interfaz para un dispositivo de E / S lenta. Si construyes de manera inteligente una interfaz singleton para un dispositivo lento, y nadie la llama nunca, no perderás tiempo. Si otro fragmento de código lo llama desde varios lugares, su singleton puede optimizar el almacenamiento en caché para ambos simultáneamente y evitar búsquedas dobles. También puede evitar fácilmente cualquier condición de interbloqueo en el recurso controlado por singleton.
Contra singletons:
- En C ++, no hay una buena manera de auto-limpiar después de los singletons. Hay soluciones alternativas, y formas ligeramente extravagantes para hacerlo, pero simplemente no hay una forma simple y universal de asegurarse de que siempre se invoque el destructor de su singleton. Esto no es tan terrible en cuanto a la memoria, solo piense en ello como variables más globales, para este propósito. Pero puede ser malo si su singleton asigna otros recursos (por ejemplo, bloquea algunos archivos) y no los libera.
Mi propia opinion:
Uso singletons, pero evítelos si hay una alternativa razonable. Esto ha funcionado bien para mí hasta ahora, y he encontrado que se pueden probar, aunque un poco más de trabajo para probar.
Encuentro que debes ser muy cuidadoso acerca de por qué estás decidiendo usar un singleton. Como otros han mencionado, es esencialmente el mismo problema que usar variables globales. Debe ser muy cauteloso y considerar lo que podría estar haciendo al usar uno.
Es muy raro usarlos y, por lo general, hay una mejor manera de hacer las cosas. Me encontré con situaciones en las que hice algo con un singleton y luego tuve que revisar mi código para sacarlo después de descubrir cuánto empeoraba las cosas (o después de encontrar una solución mucho mejor y más sana). )
Escriba objetos inyectables normales, comprobables y deje que Guice / Spring / lo que sea maneje la instanciación. Seriamente.
Esto se aplica incluso en el caso de cachés o cualquiera que sean los casos de uso natural para singletons. No hay necesidad de repetir el horror de escribir código para intentar imponer una instancia. Deje que su marco de inyección de dependencia lo maneje. (Recomiendo a Guice un contenedor DI liviano si aún no está usando uno).
Estoy leyendo mucho sobre "Singleton", sus problemas, cuándo usarlo, etc., y estas son mis conclusiones hasta ahora:
Confusión entre la implementación clásica de Singleton y el requisito real: ¡TENER UNA SOLA INSTANCIA DE CLASE!
En general, está mal implementado. Si desea una instancia única, no use el patrón (anti) de usar un método estático GetInstance () que devuelva un objeto estático. Esto hace que una clase sea responsable de instanciar una sola instancia de sí mismo y también realizar lógica. Esto rompe el Principio de Responsabilidad Individual . En cambio, esto debería ser implementado por una clase de fábrica con la responsabilidad de garantizar que solo exista una instancia.
Se usa en constructores porque es fácil de usar y no se debe pasar como parámetro. Esto se debería resolver usando la inyección de dependencia , que es un gran patrón para lograr un modelo de objeto bueno y comprobable.
No TDD . Si usa TDD, las dependencias se extraen de la implementación porque desea que sus pruebas sean fáciles de escribir. Esto hace que su modelo de objeto sea mejor. Si usa TDD, no escribirá GetInstance estático =). Por cierto, si piensas en objetos con responsabilidades claras en lugar de clases, obtendrás el mismo efecto =).
Google tiene un Singleton Detector para Java que creo que comenzó como una herramienta que debe ejecutarse en todos los códigos producidos en Google. La razón básica para eliminar Singletons:
porque pueden dificultar las pruebas y ocultar problemas con su diseño
Para una explicación más explícita, vea " Por qué los Singletons son polémicos " de Google.
Hay tres publicaciones de blog bastante buenas sobre Singletons por Miško Hevery en el blog de Google Testing.
He estado tratando de pensar en una forma de llegar al rescate del pobre Singelton aquí, pero debo admitir que es difícil. He visto muy pocos usos legítimos de ellos y con el impulso actual de hacer la inyección de dependencias y las pruebas unitarias son simplemente difíciles de usar. Definitivamente son la manifestación del "culto a la carga" de la programación con patrones de diseño. He trabajado con muchos programadores que nunca han descifrado el libro "GoF", pero conocen a "Singelton" y conocen "Patrones".
Sin embargo, tengo que estar en desacuerdo con Orion, la mayoría de las veces que he visto el uso excesivo de singeltons no son variables globales en un vestido, sino más bien servicios (métodos) globales en un vestido. Es interesante observar que si intenta usar Singeltons en SQL Server 2005 en modo seguro a través de la interfaz CLR, el sistema marcará el código. El problema es que tiene datos persistentes más allá de cualquier transacción determinada que pueda ejecutarse, por supuesto, si hace que la variable de instancia sea solo de lectura, puede evitar el problema.
Ese problema me llevó a un gran trabajo un año.
He usado singletons un montón de veces en conjunto con Spring y no lo consideré una muleta o flojo.
Lo que este patrón me permitió hacer fue crear una única clase para un conjunto de valores de tipo de configuración y luego compartir la instancia única (no mutable) de esa instancia de configuración específica entre varios usuarios de mi aplicación web.
En mi caso, el singleton contenía los criterios de configuración del cliente (ubicación del archivo css, criterios de conexión db, conjuntos de características, etc.) específicos para ese cliente. Estas clases fueron creadas y accedidas a través de Spring y compartidas por usuarios con la misma configuración (es decir, 2 usuarios de la misma empresa). * ** Sé que hay un nombre para este tipo de aplicación, pero me está escapando *
Creo que habría sido un desperdicio crear (luego recoger basura) nuevas instancias de estos objetos "constantes" para cada usuario de la aplicación.
Los polluelos me cavan porque rara vez uso singleton y cuando lo hago es típicamente algo inusual. No, en serio, me encanta el patrón singleton. ¿Sabes por qué? Porque:
- Soy perezoso.
- Nada puede salir mal
Claro, los "expertos" hablarán sobre "pruebas unitarias" e "inyección de dependencia", pero eso es todo una carga de riñones de dingo. ¿Dices que el singleton es difícil de probar? ¡No hay problema! Simplemente declare todo público y convierta su clase en una casa divertida de bondad global. ¿Recuerdas el espectáculo Highlander de la década de 1990? El singleton es algo así porque: A. Nunca puede morir; y B. Solo puede haber uno. Así que deja de escuchar todas esas desilusiones de DI e implementa tu singleton con abandono. Aquí hay algunas más buenas razones ...
- Todos lo están haciendo.
- El patrón singleton te hace invencible.
- Singleton rima con "ganar" (o "diversión" dependiendo de su acento).
Los singleton tienen su utilidad, pero hay que tener cuidado al usarlos y exponerlos, porque son demasiado fáciles de abusar , difíciles de probar realmente como unidades, y es fácil crear dependencias circulares basadas en dos singletons que se acceden entre sí.
Sin embargo, es útil cuando desea asegurarse de que todos sus datos estén sincronizados en varias instancias, por ejemplo, las configuraciones para una aplicación distribuida, por ejemplo, pueden depender de singleton para asegurarse de que todas las conexiones utilicen el mismo nivel de actualización. fecha de conjunto de datos.
Los singletons son muy útiles, y su uso no es en sí mismo un antipatrón. Sin embargo, han obtenido una mala reputación en gran parte porque obligan a cualquier código de consumo a reconocer que son un singleton para interactuar con ellos. Eso significa que si alguna vez necesita "des-Singletonize" ellos, el impacto en su base de código puede ser muy significativo.
En cambio, sugeriría esconder el Singleton detrás de una fábrica. De esta forma, si necesita modificar el comportamiento de creación de instancias del servicio en el futuro, puede cambiar la fábrica en lugar de todos los tipos que consuman Singleton.
¡Aún mejor, use un contenedor de inversión de control! La mayoría de ellos le permiten separar el comportamiento de creación de instancias de la implementación de sus clases.
Muchas aplicaciones requieren que haya solo una instancia de alguna clase, por lo que el patrón de tener solo una instancia de una clase es útil. Pero hay variaciones sobre cómo se implementa el patrón.
Existe el singleton estático , en el que la clase obliga a que solo haya una instancia de la clase por proceso (en Java, en realidad, uno por ClassLoader). Otra opción es crear solo una instancia .
Los singleton estáticos son malvados , un tipo de variables globales. Hacen más difícil la prueba, porque no es posible ejecutar las pruebas en total aislamiento. Necesita una configuración complicada y un código de desmontaje para limpiar el sistema entre cada prueba, y es muy fácil olvidarse de limpiar algún estado global correctamente, lo que a su vez puede dar como resultado un comportamiento no especificado en las pruebas.
Crear solo una instancia es bueno . Simplemente crea una instancia cuando se inicia el programa y luego pasa el puntero a esa instancia a todos los demás objetos que lo necesiten. Los marcos de inyección de dependencias lo hacen fácil: usted simplemente configura el alcance del objeto, y el marco DI se encargará de crear la instancia y pasarla a todos los que lo necesiten. Por ejemplo, en Guice usted anotaría la clase con @Singleton, y el marco DI creará solo una instancia de la clase (por aplicación: puede tener múltiples aplicaciones ejecutándose en la misma JVM). Esto facilita las pruebas, ya que puede crear una nueva instancia de la clase para cada prueba y dejar que el recolector de basura destruya la instancia cuando ya no se use. Ningún estado global se fugará de una prueba a otra.
Para más información: The Clean Code Talks - "Global State and Singletons"
Realmente no estoy de acuerdo con el conjunto de variables globales en una idea de disfraces . Los singletons son realmente útiles cuando se usan para resolver el problema correcto. Déjame darte un verdadero ejemplo.
Una vez desarrollé una pequeña pieza de software en un lugar donde trabajaba, y algunos formularios tenían que usar información sobre la compañía, sus empleados, servicios y precios. En su primera versión, el sistema cargaba esos datos de la base de datos cada vez que se abría un formulario. Por supuesto, pronto me di cuenta de que este enfoque no era el mejor.
Luego creé una clase singleton, llamada compañía , que encapsulaba todo sobre el lugar, y estaba completamente llena de datos cuando se abrió el sistema.
No se trataba solo de un conjunto de variables en un disfraz porque esto tenía docenas de responsabilidades, como comunicarse con la capa de persistencia para guardar / recuperar datos sobre la empresa, tratar con los empleados y las colecciones de precios, etc.
Además, era un punto fijo, de fácil acceso para todo el sistema para tener los datos de la compañía.
Singleton no es un patrón horrible, aunque se usa mucho mal . Creo que este mal uso se debe a que es uno de los patrones más fáciles y la mayoría de los nuevos en el singleton se sienten atraídos por el efecto secundario global.
Erich Gamma había dicho que el singleton es un patrón que él desea no estaba incluido en el libro de GOF y es un mal diseño. Tiendo a estar en desacuerdo.
Si el patrón se usa para crear una sola instancia de un objeto en un momento dado, entonces el patrón se está utilizando correctamente. Si el singleton se usa para dar un efecto global, se está utilizando incorrectamente.
Desventajas:
- Estás uniendo a una clase en todo el código que llama al singleton
- Crea un problema con la prueba unitaria porque es difícil reemplazar la instancia con un objeto simulado
- Si el código necesita ser refactorizado más adelante debido a la necesidad de más de una instancia, es más doloroso que si la clase singleton se pasara al objeto (usando una interfaz) que lo usa
Ventajas:
- Una instancia de una clase se representa en cualquier punto dado en el tiempo.
- Por diseño, usted está imponiendo esto
- Instance se crea cuando es necesario
- El acceso global es un efecto secundario
Singleton como un detalle de implementación está bien. Singleton como interfaz o como mecanismo de acceso es un PITA gigante.
Un método estático que no toma parámetros devolviendo una instancia de un objeto es solo ligeramente diferente de simplemente usar una variable global. Si en cambio un objeto tiene una referencia al objeto singleton pasado, ya sea a través de un constructor u otro método, entonces no importa cómo se crea realmente el singleton y todo el patrón resulta no importar.
Un singleton es solo un grupo de variables globales en un vestido elegante.
Las variables globales tienen sus aplicaciones, como lo hacen los singleton, pero si crees que estás haciendo algo genial y útil con un singleton en lugar de usar una variable global asquerosa (todos saben que los globales son malos), desafortunadamente estás equivocado.
Una cosa que da miedo en singletons en Java, por ejemplo, es que en algunos casos puedes terminar con varias instancias del mismo singleton. La JVM se identifica de forma única en función de dos elementos: el nombre completo de una clase y el cargador de clases responsable de cargarlo.
Eso significa que dos cargadores de clases pueden cargar la misma clase sin darse cuenta entre sí, y diferentes partes de su aplicación tendrían diferentes instancias de este singleton con el que interactuarían.
Uno de los colegas con los que he trabajado era muy soltero. Cada vez que había algo que era una especie de objeto de gerente o jefe, lo convertía en un singleton, porque se imaginaba que debería haber un solo jefe. Y cada vez que el sistema asumió algunos requisitos nuevos, resultó que había razones perfectamente válidas para permitir múltiples instancias.
Yo diría que Singleton debería usarse si el modelo de dominio dicta (no ''sugiere'') que existe uno. Todos los demás casos son simplemente instancias únicas de una clase.