fbpx

Singleton – Patrones de diseño en JAVA

En la programación orientada a objetos, una clase singleton es una clase que solo puede tener un objeto (una instancia de la clase) a la vez. Después de la primera vez, si intentamos instanciar la clase Singleton, la nueva variable también apunta a la primera instancia creada. Entonces, cualquier modificación que hagamos a cualquier variable dentro de la clase a través de cualquier instancia, afecta la variable de la única instancia creada y es visible si accedemos a esa variable a través de cualquier variable de ese tipo de clase definido.

Creando una clase Singleton

El enfoque más popular es implementar un Singleton creando una clase regular y asegurándose de que tenga:

  • Un constructor privado
  • Un campo estático que contiene una única instancia
  • Un método estático para obtener la instancia.

Un ejemplo podría ser:

public final class ClaseSingleton {

    private static ClaseSingleton INSTANCE;
    
    private ClaseSingleton() {        
    }
    
    public static ClaseSingleton getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new ClaseSingleton();
        }
        
        return INSTANCE;
    }

}

¿Por qué usar Singleton?

Problema

Digamos que deseas usar una clase varias veces, ya que generalmente tiene información que debe usarse varias veces y también ser fácilmente accesible.

Un enfoque para la solución es crear una instancia de clase en cada método que lo requiera y usar tanto como desee

public class AwesomeConfig {

    private final String version;

    public AwesomeConfig(final String version) {
        this.version = version;
    }

    public String getVersion() {
        return version;
    }
}

La forma de poder usar nuestra clase es

public class AmazingService {

    public void amazingLogic() {
        final AwesomeConfig awesomeConfig = new AwesomeConfig("1.0.0");

        if ("1.0.0".equals(awesomeConfig.getVersion())) {
            System.out.println("Necesitamos actualizar");
        } else {
            System.out.println("La version es correcta");
        }
    }

    public void awesomeAlgorithm() {
        final AwesomeConfig awesomeConfig = new AwesomeConfig("2.0.0");

        if ("1.0.0".equals(awesomeConfig.getVersion())) {
            System.out.println("El algoritmo no funciona aqui");
        } else {
            System.out.println("Todo bien");
        }
    }
}

Por supuesto, esto nos trae mucha complejidad y código repetitivo. ¿Necesitamos instanciar nuestra versión cada vez para un método? ¿Qué pasa si la versión cambia y queremos llegar a diferentes lugares?

Puedes refactorizarlo a un método de clase como el siguiente, pero está creando un objeto cada vez que se requiere, creando una pérdida de memoria:

public class AmazingService {

    public void amazingLogic() {
        final AwesomeConfig awesomeConfig = getAwesomeConfig();

        if ("1.0.0".equals(awesomeConfig.getVersion())) {
            System.out.println("We need to update");
        } else {
            System.out.println("All good");
        }
    }

    public void awesomeAlgorithm() {
        final AwesomeConfig awesomeConfig = getAwesomeConfig();

        if ("1.0.0".equals(awesomeConfig.getVersion())) {
            System.out.println("This algorithm doesn't work here");
        } else {
            System.out.println("All good");
        }
    }

    private AwesomeConfig getAwesomeConfig() {
        return new AwesomeConfig("1.0.0");
    }

}

Otra forma de evitar esta pérdida de memoria es creando una variable de clase estática, resuelve la creación de múltiples objetos y está accesible para toda la clase:

public class AmazingService {

    private final static AwesomeConfig awesomeConfig = new AwesomeConfig("1.0.0");

    public void amazingLogic() {
        if ("1.0.0".equals(awesomeConfig.getVersion())) {
            System.out.println("We need to update");
        } else {
            System.out.println("All good");
        }
    }

    public void awesomeAlgorithm() {
        if ("1.0.0".equals(awesomeConfig.getVersion())) {
            System.out.println("This algorithm doesn't work here");
        } else {
            System.out.println("All good");
        }
    }

}

El principal problema con este enfoque es que solo es accesible para la clase que posee la variable y necesita crear una variable de clase estática cada vez que necesita acceder a ella en diferentes clases, creando nuevamente, inconsistencia y múltiples objetos para la misma clase.

Usando Singleton

Una solución de bastante interesante es usar el patrón singleton, cambiar nuestra clase de configuración para tener solo una instancia estática, podría usarla en su proyecto y solo crear una instancia de él. La forma de implantarla sería la siguiente:

public class AwesomeConfig {

    private static final AwesomeConfig instance = new AwesomeConfig("1.0.0");
    private String version;

    private AwesomeConfig(String version) {
        this.version = version;
    }

    public static AwesomeConfig getInstance() {
        return instance;
    }

    public String getVersion() {
        return version;
    }
}

De esta forma tenemos una única instancia de la clase AwesomeConfig sin tener que recurrir a crearla continuamente y además accesible en todo nuestro código de forma estática. Podemos cambiar nuestro código como en el siguiente ejemplo:

public class AmazingService {
    
    public void amazingLogic() {
        final AwesomeConfig config = AwesomeConfig.getInstance();
        if ("1.0.0".equals(config.getVersion())) {
            System.out.println("We need to update");
        } else {
            System.out.println("All good");
        }
    }

    public void awesomeAlgorithm() {
        final AwesomeConfig config = AwesomeConfig.getInstance();
        if ("1.0.0".equals(config.getVersion())) {
            System.out.println("This algorithm doesn't work here");
        } else {
            System.out.println("All good");
        }
    }

}