Menu
Coddy logo textTech

Patrón Singleton

Parte de la sección Programación Orientada a Objetos del Journey de Java de Coddy — lección 65 de 87.

El Patrón Singleton es un patrón de diseño creacional que garantiza que una clase tenga una única instancia en toda su aplicación, a la vez que proporciona un punto de acceso global a dicha instancia. Esto es útil para recursos que deben ser compartidos, como gestores de configuración, pools de conexiones o servicios de registro (logging).

El patrón funciona haciendo que el constructor sea privado, lo que impide la instanciación directa. En su lugar, un método estático controla el acceso a la instancia única:

public class DatabaseConnection {
    private static DatabaseConnection instance;
    
    // El constructor privado evita la instanciación directa
    private DatabaseConnection() {
        System.out.println("Connection created");
    }
    
    // Punto de acceso global
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
    
    public void query(String sql) {
        System.out.println("Executing: " + sql);
    }
}

Cada llamada a getInstance() devuelve el mismo objeto. La instancia se crea solo cuando se solicita por primera vez; esto se llama inicialización perezosa:

DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();

System.out.println(db1 == db2);  // true - misma instancia

Los componentes clave de un Singleton son: un campo private static que contiene la instancia, un constructor private y un método public static que devuelve la instancia. Esta combinación garantiza que, sin importar cuántas veces solicites el objeto, siempre obtendrás el mismo.

challenge icon

Desafío

Fácil

¡Vamos a construir un administrador de configuración para una aplicación utilizando el Patrón Singleton! Tu administrador de configuración asegurará que todas las partes de tu aplicación compartan los mismos ajustes, sin importar cuántas veces soliciten acceso a él.

Organizarás tu código en dos archivos:

  • AppConfig.java: Crea una clase Singleton que gestione los ajustes de configuración de la aplicación. Tu AppConfig debe tener:

    Un campo estático privado para mantener la instancia única.

    Un campo String privado llamado appName y un campo int privado llamado maxUsers.

    Un constructor privado que inicialice appName a "MyApplication" y maxUsers a 100, y luego imprima AppConfig initialized.

    Un método público estático getInstance() que cree la instancia solo si aún no existe (inicialización diferida o lazy initialization) y luego la devuelva.

    Un método setAppName(String name) que actualice el nombre de la aplicación.

    Un método setMaxUsers(int max) que actualice el máximo de usuarios.

    Un método displaySettings() que imprima App: [appName], Max Users: [maxUsers].

  • Main.java: ¡Demuestra que tu Singleton funciona correctamente! Recibirás dos entradas: un nuevo nombre de aplicación (String) y un nuevo valor de máximo de usuarios (entero).

    Primero, obtén una instancia de AppConfig y almacénala en una variable llamada config1. Llama a displaySettings() para mostrar los valores por defecto.

    Luego, actualiza los ajustes utilizando los valores de entrada llamando a setAppName y setMaxUsers en config1.

    Obtén otra instancia y almacénala en config2. Llama a displaySettings() en config2 para demostrar que refleja los cambios realizados a través de config1.

    Finalmente, imprime si ambas referencias apuntan al mismo objeto: Same instance: [true/false] comparando config1 == config2.

Recibirás dos entradas en orden: el nuevo nombre de la aplicación (String) y el nuevo máximo de usuarios (entero).

Este desafío demuestra el beneficio principal del Patrón Singleton: ¡no importa cuántas veces solicites la configuración, siempre obtendrás la misma instancia con el mismo estado compartido!

Hoja de referencia

El Patrón Singleton garantiza que una clase tenga una sola instancia en toda la aplicación, proporcionando un punto de acceso global a dicha instancia.

Componentes Clave:

  • Un campo private static que contiene la instancia única
  • Un constructor private para evitar la instanciación directa
  • Un método public static que devuelve la instancia

Implementación Básica:

public class DatabaseConnection {
    private static DatabaseConnection instance;
    
    // Private constructor prevents direct instantiation
    private DatabaseConnection() {
        System.out.println("Connection created");
    }
    
    // Global access point
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
    
    public void query(String sql) {
        System.out.println("Executing: " + sql);
    }
}

Uso:

DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();

System.out.println(db1 == db2);  // true - same instance

La instancia se crea solo cuando se solicita por primera vez; esto se llama inicialización diferida (lazy initialization). Cada llamada a getInstance() devuelve el mismo objeto, lo que garantiza un estado compartido en toda la aplicación.

Pruébalo tú mismo

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Leer entradas
        String newAppName = scanner.nextLine();
        int newMaxUsers = scanner.nextInt();
        
        // TODO: Obtener la primera instancia de AppConfig y almacenarla en config1
        
        // TODO: Llamar a displaySettings() en config1 para mostrar los valores por defecto
        
        // TODO: Actualizar la configuración usando setAppName y setMaxUsers en config1
        
        // TODO: Obtener otra instancia y almacenarla en config2
        
        // TODO: Llamar a displaySettings() en config2 para demostrar que los cambios se comparten
        
        // TODO: Imprimir si ambas referencias apuntan al mismo objeto:
        // "Same instance: [true/false]" usando config1 == config2
    }
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos