DEV Community

Elías Canales
Elías Canales

Posted on

Singleton

Patrón creacional para mantener una única instancia

El patrón singleton es un patrón que hace que una clase solo tenga una instancia. Para ello el principal objetivo es evitar que se pueda instanciar la clase (desde otra clase). Lo cual se puede conseguir con el modificador de acceso private.

public class Singleton {

    public static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

}
Enter fullscreen mode Exit fullscreen mode

También podemos cerrar el acceso directo a INSTANCE, y permitir su acceso a la instancia por medio de un método.

public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
      return INSTANCE;
    }

}
Enter fullscreen mode Exit fullscreen mode

Lo recomendado es usar un atributo publico, pero si necesitas hacer algo más, o simplemente esa opción no encaja en tu desarrollo, puedes utilizar un método.

Aún así tenemos dos problemas que resolver. El primer problema es la serialización. Cuando tenemos un objeto serializado y luego lo deserializamos, vamos a encontrar que ya no tenemos una sola instancia, sino dos.

Para comprobarlo puedes hacer un test como este.

Singleton original = Singleton.INSTANCE;

final ByteArrayOutputStream byteArrayOutputStream = 
  new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(original);
out.close();

ObjectInputStream in = new ObjectInputStream(
  new ByteArrayInputStream(
        byteArrayOutputStream.toByteArray()
  )
);
Singleton deserialized = (Singleton) in.readObject();
in.close();

assertSame(original, deserialized);
Enter fullscreen mode Exit fullscreen mode

Cuando se ejecute assertSame dará error, esto es porque llegados a ese punto tendrás dos instancias distintas. Para arreglarlo, tenemos que modificar el método de la serialización readResolve().

public class Singleton implements Serializable {

    public static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    @Serial
    public Object readResolve() {
        return INSTANCE;
    }

}
Enter fullscreen mode Exit fullscreen mode

Ahora ya sabemos que la serialización no es un problema, sin embargo, tenemos un segundo problema, y es reflection.

Con reflection en Java podemos permitir la instanciación, y siendo capaces de volver a llamar al constructor, tendremos dos instancias nuevamente.

final Class<Singleton> clazz = Singleton.class;
final Constructor<Singleton> constructor = 
  clazz.getDeclaredConstructor();
constructor.setAccessible(true);

//Crea una nueva instancia del Singleton
constructor.newInstance();
Enter fullscreen mode Exit fullscreen mode

Para resolver este problema podemos lanzar una excepción cuando el constructor sea llamado por segunda vez.

private Singleton() {
    if (INSTANCE != null) {
        throw new AssertionError("Constructor not allowed");
    }
}
Enter fullscreen mode Exit fullscreen mode

Ahora ya tenemos una clase Singleton donde evitamos dos de los problemas principales. Aunque también hay otra forma de hacer el Singleton, y es usando un Enum.

Lo bueno que tiene usar el Enum, es que los dos problemas descritos anteriormente están resueltos sin hacer nada. Aunque en este enfoque no podremos heredar de otra clase.

public enum SingletonEnum {
    INSTANCE;
}
Enter fullscreen mode Exit fullscreen mode

Referencias
Joshua Bloch, Effective Java (3ª edición), Addison-Wesley, 2018.

Top comments (0)