Propuesta de Singleton Pattern
Estimados compañeros:
Hace unas horas, estudiando el patrón de diseño OO Singleton, me surgió la siguiente idea para construir una propuesta del mismo, sin utilizar la clase anidada estática "Holder" y con esto simplificar el diseño, aunque creo que tiene sus desventajas.
Este constructo del Singleton está basado en la premisa de que una variable miembro estática es compartida por todas las instancias que la llamen, así que no encuentro ningún otro inconveniente más que la inicialización temprana de la clase y no bajo demanda. La clase también funciona en entorno multi-thread, aunque debo admitir que no he hecho pruebas muy exhaustivas donde ponga a prueba el rendimiento con la concurrencia de muchas clases.
Me gustaría que me dieran su opinión de este código, ya que soy relativamente nuevo en este tema y no he encontrado mucha ayuda en la red ni con mis amigos ya que no les gusta la programación.
Ya que estoy preparando el examen de Java Professional Certified de Oracle, su experiencia y sus comentarios serían de mucha utilidad para mi entrenamiento.
El código es el siguiente:
El libro que estoy estudiando para el examen es "Oracle Certified Professional Java SE 7" de S G ganesh y Tushar Sharma :)
De antemano les doy las gracias por su atención y buenas noches!!!.
Observación
Suponiendo que yo corriera este pequeño ejemplo:
Te das cuenta que no estás protegiendo tu singleton contra escritura? i.e. cualquiera podría hacer la referencia nula. Pero mas alla de los ejercicios y esas cosas: los singletons se consideran mayoritariamente una mala practica y hasta dañinos:
What is so bad about Singletons?
Proteger el singleton contra escritura
Efectivamente, la clase ha quedado expuesta ya que la variable miembro "instance" la declaré pública, sin embargo, no me resulta del todo claro el por qué este patrón es mala práctica, aunque lo estudiaré con más calma. ¿Qué pasa entonces cuando quieres crear una instancia global para la aplicación? Por ejemplo un objeto que mantenga concentrado el logging de la api (con todas sus clases) en un único objeto. Supongo desde ahí que quizá haya otro patrón de diseño que solvente esta problemática. Por otro lado, quizá la solución para este ejemplo, aunque rudimentaria y gracias a tu comentario, podría ser:
Quizá este ejemplo tenga protección contra escritura.
Por otro lado y gracias a tu comentario me pondré a investigar lo dañino que puede ser este patrón, aunque seguiré practicándolo ya que es parte de lo que te preguntan en el examen de certificación profesional en Oracle
Buenas noches
¿Qué pasa entonces cuando
¿Qué pasa entonces cuando quieres crear una instancia global para la aplicación
Usas inyección de dependencias para proporcionar una sola instancia en sus dependencias.
El problema con el que más me encuentro usando un singleton por classloader es que una vez que haces una instancia estática (y como con todo lo estático) ya no puedes testear el código cliente sin testear la dependencia estática; a menos que uses librerías para hacer pruebas tapando el problema de fondo e.g. usar PowerMocks para hacerle un “mock” al método
.
Siempre puedes hacerle refactoring a ese singleton pero también tienes que ponerte a pensar si es “thread-safe” y aparte la lógica necesaria si la instancia necesita configuraciones (tendrías que hacer que el propio objeto se configurara solo lo cual también es una bronca) e.g.:
Logging
Pues este es un code snippet muy usado con SLF4J:
Aqui en realidad estas usando un factory para crear una instancia del logger que necesitas, que en todo caso no seria un singleton por classloader. Ya esta instancia viene configurada por el factory con lo necesario para hacer logging decente y maneja tambien una cadena de responsabilidad para los niveles; todo concentrado en este objeto y generalizando (abstrayendo) lo que mencionas.
LoggerFactory y pruebas
En el caso de este framework slf4j entiendo que la fabrica produce una instancia del Logger de acuerdo al parámetro que se pasa al método getLogger(MyClass.class), que es el que le da el nombre interno al logger; aunque le puedes pasar cualquier cadena. Ahora, LoggerFactory está construida sobre el patrón Factory en combinación con una estrategia para crear una sola instancia del logger, si se provee el mismo parámetro (o la misma cadena de caracteres) al método getLogger. Derivado de esto mi pregunta es ¿Cómo se llama la estrategia usada en esta fábrica para crear una sola instancia del logger? y ¿Cómo enfrenta esta instancia un entorno multi-thread?
Concuerdo que la inyección de dependencias es lo más útil en los casos de requerir instancias únicas en el sistema, por lo que voy a explorar el tema, así como las alternativas que se ofrecen a este patrón de diseño. Sin embargo,me son muy útiles tus comentarios para ampliar mi conocimiento de este patrón, aunque más adelante evite usarlo en el mundo real, ya que es un tema que debo comprender muy bien para la certificación que estoy buscando en Oracle (1z0-804) y por lo que agradezco tu atención.
Con relación a las pruebas, he hecho lo básico con junit, aunque realmente no tengo tanta experiencia para comprender lo que me describes con relación a este tema, pero lo tomaré en cuenta en mis próximos ejercicios de pruebas unitarias y me pondré a leer más del tema.
Saludos y buenas noches
Singleton y enum
Otra solución interesante sería con
:
Ventajas con enum
En efecto, para asegurar una sola instancia, además naturalmente serializable, el tipo enum es la mejor opción. La única desventaja que he leído es que los tipos enum heredan implícitamente de java.lang.Enum, por lo que no pueden heredar de ninguna otra clase; aunque esto se soluciona con composición de clases o la implementación de interfaces, lo cual sí está permitido dentro de los enums. Sin duda el Singleton es un tema muy extenso y polémico como patrón de diseño, pero ¿Por qué es parte del examen iz0-804 de Oracle? Seguramente se usa de manera extendida en programas en el mundo real.
eh?
"una sola instancia" y "serializable" son mutuamente excluyentes.
mutuamente excluyentes
Derivado de tu comentario @ezamudio, hice una simple prueba con la opción del singleton con enum propuesta por @jpaul
Este es el código del enum:
Este es el código del main
Al parecer, al intentar hacer dos instancias del enum dos veces del archivo, sigue siendo la misma instancia. Quizá en otro tipo de prueba que desconozco, se puedan generar dos instancias de este enum, pero esta prueba me arroja la misma. Quizá debería probarla en dos máquinas virtuales distintas y, efectivamente, al serializar el enum se creará una instancia del mismo por cada máquina virtual que lo llame. Haré la prueba. Saludos y buenas noches.
Re: Observación
Claro que se consideran mala práctica cuando se usan de manera incorrecta. Hay muchos casos para los que funcionan, por ejemplo, la caché de un website, ¿es que vas a andar creando un objeto de caché por cada sesión o peor aún, por cada petición?.
Incluso en la pregunta de StackOverflow vienen unas consideraciones si te planteas utilizar singleton.
Y pues eso, dudo que sea una mala práctica o que sea dañino, creo que tienen su lugar y su razón de ser, depende que caso y resuelve un problema el cual es cuando un objeto necesita ser compartido entre varias instancias.
Re: mutuamente excluyentes
¿Pero que problema tiene la manera tradicional?
La manera tradicional
El problema que he estudiado en el caso de un Singleton de la manera que lo planteas @wishmaster77, tiene que ver cuando utilizas MySingleton en un entorno Multi-hilo, por lo que hice esta prueba del libro con la misma clase que escribiste (MySingleton), la cual demuestra que se pueden crear varias instancias de esta clase, cuando no debería de ser posible, según el propósito de un singleton. También, en el caso de serializarla y realizar la prueba que hice con la versión "enum" del singleton, también puedes "transgredir" la propiedad de solo poder crear una sola instancia de esta clase para la aplicación.
Este es el código de prueba para la clase que propones (yo propuse una versión similar al principio de este post)
Realicé varias pruebas en las que se creó más de una instancia de MySingleton y algunas ocasiones me resultaban algunas referencias null (extraño jaja). Este fue el último resultado que obtuve:
En las líneas dos y tres se crean otras dos instancias, como puedes ver. Por eso esta forma no es útil en un entorno multi-thread. Bueno, esa es la mejor explicación que encuentro jeje. Por otra parte, sigo pensando que los Singletons son útiles, y por algo los preguntan en el examen de certificación 1z0-804 de Oracle. Saludos y buenas tardes :)
Como ya se comento, ese
Como ya se comento, ese codigo no es thread-safe.
Claro que se consideran mala
Pero... dime una manera correcta?
No es que no la haya pero pienso que si la hubiera podrías usar alternativas que probablemente serian mejor. En todo caso la única vez que probablemente he visto un uso del singleton (que en realidad me gusta mas como un null object o un flyweight) es en la clase Optional de Guava con el objeto Absent.
Re: La manera tradicional
En el caso de multi-hilos concuerdo con @ElderMael, se usa inyección de dependencias y te aseguras que la inyección se realice al iniciar la aplicación.
Re: Claro que se consideran mala
Lo dicho, depende para qué. Quizá a lo que estás acostumbrado Singleton sea una mala idea, quizá para alguien más sea algo tan válido.
Re: ¿Qué pasa entonces cuando
Pero si lo que preocupa realmente es hacer código thread-safe, creo que lo mejor sería aplicar algo más funcional, libre de estados.
Singleton de estudio
Me queda claro que la inyección de dependencias es la opción para manipular la cantidad de instancias de una clase, ya que el contenedor de dependencias se encarga de gestionar esto, de acuerdo a la configuración del contenedor; esto es posible con clases que sean Singleton o no (da igual, pues la fábrica es quien controla esto, más no la propia clase). Sin embargo, el punto que planteo justo es profundizar en las ventajas y desventajas de este patrón, así como los pormenores que atraviesa su diseño para asegurar que una clase pueda ser instanciada una sola vez, con propósitos meramente estudiantiles enfocados al objetivo de pasar el examen 1zo-804, por lo que sus comentarios me han sido muy útiles ya que me han dado pautas para investigar y hacer más pruebas. De cualquier manera, en el ámbito laboral, como bien lo proponen, prefiero trabajar mis dependencias con Spring. Saludos y buenas noches.
final
Regresando al ejemplo original, sólo le falta un
a la variable estática (para que no sea variable) y ya.
Re: final
Quedando:
También es una opción.
static
Es la manera más cercana al Singleton, convertir en static fina a la referencia que contiene la instancia. Aún seguiré haciendo pruebas con el enum ya que me pareció interesante el tema de la serialización el tema de que Singleton y Serializable son mutuamente excluyentes. Lo haré con networking (rmi, socket) a ver cómo funciona, solo para saber qué es lo que pasa con el enum en este entorno, para posteriormente publicar los resultados. Muchas gracias por las aportaciones. Buenas noches a todos.
Re: static
Creo que deberías revisar esto que trata sobre el tema de serialización en enum con singleton. Es interesante.
Lo voy a revisar
Por supuesto que lo revisaré wishmaster77 gracias.
Singleton thread-safe
Saludos a la banda:
Programo en moviles, y un problema comun es cuando el usuario accede a servicios por http, lo cual es usando threads, y como la red no es muy fiable que dijamos, entonces tenemos a un cliente oprimiendo el boton y la aplicacion consultando varias veces el servicio, si no usamos el singletos, se crearian varios procesos para acceder a internet, y en ocasiones el puerto del movil va a reclamar, diciendo que ya esta siendo usado, ademas de que la informacion no va a ser 100% fiable, debido que a la mejor se va a mostrar la informacion de varios procesos sin sincronizar.
Si usas un singleton normal, sera casi lo mismo, debido que, aunque usas una misma instancia, esta va a ser usada por varios procesos, en ocasiones simultaneamente, ¿Cual fue la solucion? agregarle synchronized a algunas partes del codigo, este ejemplo funciona de maravilla y evita muchos problemas:
Interesante triple check
Interesante hacer ese triple check sincronizado en la app, pero ¿No genera problemas de rendimiento? ¿No es suficiente con hacer final la referencia "instance"?, voy a realizar pruebas con threads a ver cómo funciona este triple check. Gracias.
Queue
También podrías haber usado un
con capacidad 0 en donde los hilos esperaran a ser despachados (con o sin un ExecutorService) y basicamente tendrias el mismo resultado. O si no deseas que los threads esperen entonces igual y cancelas el task actual o le darias mas capacidad. Como un event queue de los que usan a veces en Ruby.
doble
Con doble synchronized es suficiente. El synchronized a nivel metodo en getInstance sobra, de hecho hace que el codigo sea muy ineficiente puesto que siempre SIEMPRE se hace un bloqueo al invocar el metodo. La idea de tener el doble synchronized internamente es que solamente ocurren esos bloqueos cuando se crea la instancia compartida y despues ya no se ejecutan.
La diferencia entre esto y tener la instancia en un campo final, es que con este enfoque se crea la instancia unicamente cuando alguien la pide. Si en la vida de la aplicacion nadie la requiere, no se crea nunca. Con el campo final se crea el singleton cuando se carga esa clase.
synchronized a nivel de método
Coincido con ezamudio, creo que el synchronized en la declaración del método hace más lenta la aplicación, ya que bloquea el método todas las veces que se invoca de manera concurrente. Con los bloques sincronizados dentro del método solo se bloquea una vez cuando la instancia se crea, ya que las demás veces los instance == null evaluan a false y el flujo de la ejecución ya no entra a los bloques sincronizados.
Digamos que quedaría así beto.bateria, con solo el doble check
De esta manera se obtiene el mejor performance del método
Por otro lado creo que la interface BlockingQueue tengo que estudiarla en el próximo capítulo de mi curso que se trata de threads y concurrencia.Ya estaré comentando como me fue en ese punto, porque en genéricos y colecciones no está ese tema.
Saludos a todos
Pues de que tanto hablan en
Pues de que tanto hablan en este post?
Sobre el ejemplo inicial ademas de que se puede cambiar la referencia al no ser final otro problema que crea al ser público es que crea mayor acoplamiento entre la clase y sus clientes.
La idea era hacer algo mas sencillo que el metodo "holder" pero al final parece que esta resultando mas complejo. El metodo holder es especialmente efectivo en Java por la forma en la que funciona el classloader. No aplica para otros lenguajes.
Entonces, si no importa que se cargue prematuramente esta implementación funciona bien:
Y si se necesita inicializacion tardía el metodo holder es:
Ambos son thread-safe.
Nota aparte ( y pedante ): En Java no existen métodos o atributos estáticos como en C, se llaman de: "de clase" ( atributo / variable de clase, metodo de clase ).
doble??
me parece que el doble synchronized tampoco es necesario ya que el candado para la clase esta tomado por el primero y el segundo al estar dentro del bloque solo hace repetirlo
no es necesario
No, no es necesario doble synchronized. Lo que sí es necesario es doble verificación de null:
La verificacion dentro del bloque synchronized es porque mientras no existe el singleton, pueden llegar varios hilos al primer if y se les cumple la condicion.
Métodos estáticos
Bien, este post justamente lo propuse para mejorar mi entrenamiento para el 1z0-804 con la opinión de personas experimentadas, que hay muchas en este foro. Las respuestas que he recibido conforme al patrón mismo, como los que no recomiendan usarlo, me han servido para mirar desde diferentes perspectivas el objeto de estudio y, con eso mejorar la percepción que tengo del patrón, su uso y las ventajas y/o desventajas que tiene usarlo en un entorno real. Es por eso que se ha vuelto complejo pero, sin embargo útil para el propósito del post.
Por otro lado, difiero de la afirmación que haces @OscarRyz diciendo que en java no existen los métodos estáticos. Te comento que no conozco otro lenguaje de programación más que este, y en todos (absolutamente todos) los materiales de estudio que utilicé para el examen de certificación 1z0-803 (en inglés la mayoría) incluyendo las publicaciones de Oracle y el mismo examen para tal efecto, mencionan textualmente "static method" para referirse a estos métodos, y en pocas ocasiones los mencionan como "class method", pero afirmo que usan indistintamente ambos términos, y ante eso (no sé cómo sea en otros lenguajes), es correcto llamarlos métodos estáticos o métodos de clase.
Finalmente y con algunas pruebas pendientes, creo que el libro que tengo se queda corto en la explicación del singleton, ya que aquí han surgido diversas versiones de las cuales me quedaría para efectos prácticos con 4 modelos:
Carga temprana de la instancia
Carga por demanda de la instancia
Doble check
Enum Singleton
Por lo visto y las diferentes fuentes de estudio, estos son los que más se mencionan, y qué mejor que estudiarlos y probarlos con detenimiento para un mejor aprendizaje ¿No lo creen?.
Saludos a todos y buenas noches.
Re: Pues de que tanto hablan en
Se puede decir más alto pero no más claro ;)
Un buen artículo sobre Double-Checked Locking Pattern
Este artículo es ya viejo. Vale la pena digerirlo. Aunque se discute en el ámbito de C++, lo que contiene aplica en general para Java.
C++ and The Perils of Double-Checked Locking: Part I
C++ and The Perils of Double-Checked Locking: Part II<
Voy por C++
Sin duda un artículo interesante pero creo que necesito aprender C++ para entenderlo a profundidad. Lo que me queda claro es que todo tiene su complejidad por simple que parezca. Al respecto ya había leído en un libro que el double check puede (o podía) fallar en la JVM, de acuerdo a un bug con referencia al manejo de la memoria (creo que ya se solucionó), por lo que la opción más segura que he encontrado, en un Sigleton de carga de clase diferida, creo que la mejor opción es la que contiene la clase anidada Holder, ya que carga la instancia hasta que esta es requerida y no contiene métodos sincronizados. Buena tarde a todos.
cerrando
En fin desde hace tiempo quería cerrar este tema porque es duplicado de este otro, que además ya está resuelto también: