5 formas de implementar el patrón Singleton en Java

Escrito por picodotdev el .
java planeta-codigo
Enlace permanente Comentarios

El patrón Singleton es un patrón de diseño muy utilizado, este patrón garantiza que de una clase solo haya una única instancia. En Java hay varias formas de implementar el patrón, algunas son más sencillas otras no son thread-safe o con evaluación lazy.

Los lenguajes orientados a objetos modelan los programas utilizando clases, estas clases contienen tanto los datos como las operaciones que tratan esos datos que solo pueden ser modificados a través de esas operaciones. Este principio de encapsulación tiene como beneficio garantizar que los datos no sean modificados por cualquier otro código distinto a las operaciones y que los datos no queden en un estado inconsistente.

En un lenguaje orientado a objetos de cada clase se crean tantas instancias u objetos como se necesiten en el programa. En algunos casos de una clase en concreto sólo se desea que haya una única instancia, normalmente hacer que de una clase en concreto haya una única instancia se implementa con el patrón Singleton.

En Java hay varias formas de implementar el patrón Singleton, aunque todas son posibles cada una tiene diferentes propiedades, algunas tiene más beneficios sin las debilidades de otras. En este artículo incluyo 5 formas de cómo implementar el patrón Singleton con un ejemplo de código de cada una de ellas e indico cuales de ellas son las mejores opciones de entre estas.

Qué es el patrón Singleton

El patrón Singleton es un patrón de diseño que restringe a una única instancia de objeto el número instancias de una clase que se pueden crear durante la ejecución del programa. Es útil en ciertas clases donde su única instancia es compartida en todo el código como si de una variable global se tratase. Si el método que permite obtener la referencia a la instancia es un método estático la instancia es como si fuese una variable global dado que es posible acceder a ella desde cualquier parte del programa siempre que lo permitan los modificadores de acceso.

Los casos de uso de las clases singleton son mantener un estado compartido, permitir inicialización bajo demanda o lazy, también es usado en otros patrones como el patrón abstract factory, factory method, el patrón builder o facade.

Formas de implementar el patrón Singleton en Java

En Java hay varias formas de implementar el patrón Singleton en una clase, el objetivo y resultado es el mismo pero la implementación hace que tengan algunas diferencias como que no sea thread-safe, no sea lazy, otras implementaciones igualmente sencillas son thread-safe y lazy al mismo tiempo.

Esta son las implementaciones más comunes, dependiendo de las necesidades de la aplicación todas son válidas simplemente la implementación con la clase inner y enum son las que proporcionan thread-safe y evaluación lazy que algunas de las otras no tienen.

Forma tradicional

La forma tradicional de implementar el patrón Singleton es utilizando una variable estática privada para guardar la referencia de la única instancia, hacer el constructor privado de modo que el resto de clases no tengan la posibilidad de crear más instancias y un método que crea la instancia si no ha sido creada con anterioridad con una sentencia condicional y devuelve la referencia si ya existe la instancia.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
...

public class Singleton {

   private static final Singleton instance;

   private Singleton() {}

   public static Singleton getInstance() {
       if (instance == null) {
           instance = new Singleton();
       }
       return instance;
   }
}
Singleton-1.java

Esta implementación es muy utilizada, aunque es lazy ya que la instancia no se crea hasta que se realiza la primera solicitud su inconveniente es que no es thread-safe si varios hilos intentan obtener una instancia cuando aún no hay ninguna creada.

Método sincronizado

Para implementar el patrón Singleton con la propiedad de que sea thread-safe en el caso anterior la forma más sencilla es hacer el método synchronized de modo que Java garantiza que si varios hilos intentan obtener la referencia de la instancia cuando aún no está creada sólo uno de ellos la cree.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
...

public class Singleton {

   private static final Singleton instance;

   private Singleton() {}

   public synchronized static Singleton getInstance() {
       if (instance == null) {
           instance = new Singleton();
       }
       return instance;
   }
}
Singleton-2.java

El inconveniente de esta implementación con synchronized hace que el método para obtener la instancia sea más lento debido a la propia sincronización y a la contención que se produce si hay múltiples hilos en ejecución que hacen uso del método, si el rendimiento es una consideración importante hay otras formas de implementar el patrón Singleton no mucho más difíciles que tienen mejor rendimiento.

Variable estática final

La siguiente forma de implementar el patrón Singleton es crear la instancia en la inicialización de la clase ya sea como una asignación en la variable o con el constructor estático.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...

public class Singleton {

   private static final Singleton instance = new Singleton();

   private Singleton() {}

   public static Singleton getInstance() {
       return instance;
   }
}
Singleton-3a.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
...

public class Singleton {

   private static final Singleton instance;

   static {
       instance = new Singleton();
   }

   private Singleton() {}

   public static Singleton getInstance() {
       return instance;
   }
}
Singleton-3b.java

Esta implementación no requiere utilizar una sentencia condicional if para comprobar si la instancia ya ha sido creada y es thread-safe, sin embargo, no es lazy ya que la instancia se crea cuando se inicializa la clase antes de que se haga el primer uso.

Clase inner

Esta implementación es thread-safe y tiene evaluación lazy que algunas de las anteriores no tienen. Lo que hace es utilizar una clase anidada o inner para mantener la referencia a la instancia de la clase que la contiene.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
...

class Singleton {

   private static class SingletonHolder {
      public static Singleton instance = new Singleton();
   }

   private Singleton() {}

   public static Singleton getInstance() {
       return SingletonHolder.instance;
   }
}
Singleton-4.java

Este caso tampoco hace uso de la sentencia condicional if y el propio lenguaje Java por la inicialización de las clases y propiedades static garantiza que es thread-safe.

Clases enum

Las clases enum son por definición clases cuyos enumerados son singleton y thread-safe. La particularidad de los enum es que no se pueden crear nuevas instancias adicionales a las que se definan en el código en tiempo de compilación, el número de instancias es constante, por lo demás los enum al igual que las clases pueden implementar interfaces, declarar métodos y constructores de uso en el propio enum.

1
2
3
4
5
6
7
8
...

public enum Singleton {

    INSTANCE;

    ...
}
Singleton-5.java

Conclusión

Si el Singleton debe extender una clase la mejor opción de crearlo es con la clase inner, la implementación con una clase enum también es válida. Estas son las dos implementaciones aconsejadas para implementar el patrón Singleton. La forma tradicional sigue siendo muy utilizada con números ejemplos de código en artículos y es posible utilizarla, sin embargo, dadas sus desventajas es preferible utilizar alguna de las dos aconsejadas que son igual de sencillas de implementar.

Comparte el artículo: