Patrón Singleton

El patrón Singleton forma parte de los patrones creacionales. Se trata de uno de los patrones más usados y conocidos por los desarrolladores, y también es uno de los patrones más controvertidos. El patrón Singleton se encarga de controlar que únicamente se crea una instancia de una clase en toda la aplicación mediante el uso de un único punto de acceso. La definición que se da en el libro “Design Patterns: Elements of Reusable Object-Oriented Software” es:

“Garantiza que una clase sólo tenga una instancia y proporciona un punto de acceso global a ella.”

El diagrama de definición del patrón Singleton es simple:

Diagrama sin nombre

Sólo se proporciona un punto de acceso para crear una instancia de una clase Singleton. El constructor es privado y se proporciona un método getInstance() que se encarga de proporcionar el acceso a la clase:

Image

Implementaciones del patrón Singleton

Eager initialization

En esta implementación la clase se crea cuando se carga, independientemente de si se utiliza o no. Si la instancia no utiliza muchos recursos, como ficheros, datasources, etc., esta puede ser una aproximación valida, aunque no es recomendable ya que estamos creando objetos sin necesitarlos.

Static block initialization

Es similar a la anterior. Se crea una instancia de la clase en un bloque estático que es ejecutado durante la carga de la clase, incluso antes de que el constructor sea llamado:

Lazy initialization

Aquí se retrasa la creación del la instancia del objeto hasta que se necesita por primera vez. En la primera llamada, el método comprueba si la instancia ya existe, en cuyo caso devuelve su referencia. Si no existe una instancia, entonces se creara una nueva y se devolverá su referencia.

Cuando el método getInstance() es llamado por dos threads al mismo tiempo puede provocar problemas de concurrencia y permitir la creación de dos instancias de la clase. Para solucionar este problema de concurrencia se puede utilizar la siguiente solución:

Aunque este código permite crear una única instancia de la clase, existen problemas de rendimiento. La primera vez que se llame al método getInstance() se bloqueará y creara la instancia de la clase. Pero en posteriores llamadas, cuando la instancia ha sido creada, el método se seguirá bloqueando y puede provocar problemas de rendimiento. Para optimizar este código lo que se realiza es un bloqueo únicamente en el caso en el que la instancia no haya sido creada:

Esta implementación tiene un problema. Cuando hay dos threads T1 y T2, ambos quieren crear una instancia y ambos ejecutan “instance == null”, ahora ambos threads han identificado la variable de instancia como null y asumen que deben crear una nueva instancia. Ellos secuencialmente entraran el bloque synchronized y crean una nueva instancia. Al final, tenemos dos instancias en nuestra aplicación. Para resolver este error se utiliza el double-checked. Este nos dice que hay que volver a comprobar la variable de instancia de nuevo dentro del bloque synchronized:

Initialization-on-demand holder idiom

Cuando se ejecutan operaciones que son parte de la inicialización, la JVM garantiza que son son thread safety. En esta caso la clase interna no se carga hasta que algún thread referencia a algunos de sus atributos o métodos:

Enumerados

Esta implementación es equivalente a Eager initialization excepto que es más concisa, provee un mecanismo de serialización y garantiza que únicamente existe una instancia incluso ante el uso de la serialización y la reflexión, ademas de ser thread-safe.

Rompiendo el patrón Singleton en Java

Singleton y Serialización

A veces, en los sistemas distribuidos, hay que implementar el interfaz Serializable en una clase Singleton para poder almacenar el estado en el sistema de archivos y recuperarlo después:

El problema con las clases Singleton serializadas es que cada vez que se deserializa, se creará una nueva instancia de la clase y por tanto, se crean varias instancias de una clase Singleton:

Para solucionar el problema con la serialización y conseguir que una clase singleton funcione correctamente, hay que crear el método readResolve() en la clase. Este método permite controlar que objeto es devuelto en la deserialización:

Clonar Singleton

Otra forma de conseguir tener más de una instancia de una clase Singleton es llamando al método clone cuando la clase impelemnta Clonable. Para evitar clonar una clase singleton se debe sobrescribir el método clone() y lanzar una excepción CloneNotSupportedException cuando sea llamado:

Reflexión

Usando Reflexión es posible romper el patrón Singleton en todas sus implementaciones:

Cuando ese ejecuta el código anterior, se puede comprobar que la llamada al método hashCode devuelve un valor distinto para cada instancia, lo cual rompe el patrón Singleton.

Singleton vs Static Class

El patrón Singleton tiene mucha ventajas sobre una clase estática:

  • Una clase singleton puede extender e implementar interfaces, mientras que una clase estática no puede.
  • Una clase singleton puede tener una inicialización tardía mientras que una clase estática tiene una inicialización temprana.
  • Aunque la principal ventaja es que tiene, es que una clase singleton permite el polimorfismo sin forzar al usuario a asumir que sólo hay una instancia.

Pero hay otras situaciones en las que una clase estática es mejor que un Singleton:

  • Una clase no mantiene ningún estado y sólo tiene métodos de acceso, porque los métodos estáticos son más rápidos al ser creados en tiempo de compilación.
  • Cuando se crea una clase de utilidades en las que no se tiene ningún acceso a recursos que requieran acceso único. Si esto no es así se debería utilizar una clase singleton.

Razones para no utilizarlo

Alguna de las razones por las que no se debería utilizar sería:

  • Oculta las dependencias: Un componente que usa un singleton oculta información sobre dependencias. Cuando se exponen las dependencias fuerza a pensar en ellas y a pensar cuando debemos utilizar un componente.
  • Difícil de testear: El acoplamiento oculto hace que las pruebas son muy complicadas, ya que no hay manera inyectar una instancia de prueba del singleton. Además, el estado de la singleton afecta a la ejecución de un conjunto de pruebas de tal manera que no están adecuadamente aislados entre sí.

Alternativas al Singleton

No existe una alternativa completa para el patrón Singleton pero se pueden utilizar otras alternativas dependiendo de las necesidades que tengamos. Primero debemos separar la necesidad de tener una instancia de una clase del patrón Singleton, es perfectamente razonable tener una instancia de una clase sin tener que utilizar Singleton. Cuando singleton parece la respuesta a nuestro problema, entonces podemos utilizar la siguiente alternativa:

  • Crear un interfaz y su implementacion.
  • Crear una única instancia de la implementación. Puede ser utilizando Spring.
  • Mediante Inyección de Dependencias pasar la instancia a cada uno de los componentes.

Esta alternativa nos permite tener las caracteristicas del Singleton, pero creando dependencias entre componentes de una forma mucho más clara y permitiendo una forma fácil de testearlo.