Existen muchas situaciones en el desarrollo de una aplicación, en la que se desea tener un punto de acceso único hacia algún tipo de recurso del sistema, un objeto que centralice la administración de dicho recurso que es compartido.
Entonces necesitamos tener una y sola una instancia de esta clase. No mas, solo un objeto que esté disponible para ser accesado desde cualquier parte de donde sea llamado.
Singleton
La definición para este patrón de diseño dada por GoF es:
Asegura que una clase tiene sólo una instancia y proporciona un punto global de acceso a esta.
Esta dentro de la categoría de los patrones de creación. Es uno de los más simples e involucra una sola clase que se instancia a sí misma proporcionado un punto de acceso único para tal objetivo.
El diagrama de clases es:
En el diagrama vemos:
- Una variable de clase: Referencia la instancia única de la clase.
- Un constructor privado: No permite instanciar la clase con new desde afuera.
- Un método estático: Acceso único hacia la variable(que debe ser static tambien).
El codigo seria el siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
Al usarlo:
1
Singleton s = Singleton.getInstance();
Aunque el código anterior funciona, tiene problemas en un ambiente multihilos. Que pasaria si dos o mas hilos usan el
método getInstance()
?. Se pueden crear varias instancias de la clase aunque solo una de estas instancias queda referenciada.
Una solución a esto es usar synchronized
:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
Esto soluciona nuestro problema de concurrencia y funciona bien, pero y si la clase Singleton
es altamente usada,
synchronized
es costosa y podría causar un cuello de botella. Que opciones tenemos?.
Otra opción es dejar a la máquina virtual que haga el trabajo, lo que hacemos es instanciar directamente la variable, también conocida como instanciación estática:
1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance(){
return instance;
}
}
Esta opción tiene la característica de crear la instancia al momento de cargar la clase cuando la aplicación está iniciando,
es decir una inicialización “eager”. Muchas veces es recomendable una inicialización “lazy”, es decir que solo
se crea la instancia del Singleton
cuando es usada por primera vez por la aplicacion.
Otra opción es “double-check locking” que se usan dos instrucciones if
con un synchronized
en medio:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
Este patrón de diseño genera controversias. De hecho hay quienes dicen que no es patrón de diseño y que es usado fuera del contexto adecuado.
Desventajas
Dificultad para realizar testing: Un Singleton
es como una valor de variable global. Lo global y la orientación a objeto
no son buenos amigos ya que introduce un “estado persistente”, es decir valores que se mantienen siempre dificultando el uso
de objeto de reemplazo(mock) en test.
Promociona el alto acoplamiento: Si hay algo que debe ser alto en la orientación a objetos es la cohesión y no el acoplamiento.
El Singleton
es instanciado directamente desde su propia clase promocionando el uso de métodos privados y estáticos. Esto acopla
la clase que los use además de impedir el uso adecuado de inyección de dependencias.
Restricción de ejecuciones paralelas: Aunque un objetivo del Singleton
sea la gestión de un recurso compartido esto
restringe operar de forma paralela a la aplicación y lo transforma en un cuello de botella de operaciones seriales que no
es recomendable cuando la demanda es alta.
Usos
Los usos de este patrón pueden ser varios:
Logger : Este es el ejemplo más adecuado de como usar un Singleton
. Tienes un archivo de log(recurso compartido) que
solo debe ser accesado por un proceso. Además de no permitir la creación de nuevos objetos cada vez que realiza una
operación de logging.
Configuración : Permite tener un punto de acceso único a los parámetros de configuración de una aplicación manteniendo el estado de estos durante la ejecución, independientemente de donde se cargaron los valores al iniciar el sistema, si de una base de datos o un archivo.
Pool y Factorías : Aunque estos sean igualmente patrones de diseño, usan el Singleton
como base ya que un pool o una
factoría es un objeto único que tiene un estado que ofrece como servicio, permitiendo que la aplicación acceda a ese
estado siempre que lo necesite.
Conclusiones
El Singleton
debe ser usado con sabiduría. Una forma de evitar el mal uso es no instanciarlo directamente dentro de la
clase que lo necesite como dependencia. Para realizar esto lo recomendable es que el Singleton
implemente una interface
para hacer flexible el uso de inyección de dependencias y el testing.