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 instanciaLos 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.
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. TuAppConfigdebe tener:Un campo estático privado para mantener la instancia única.
Un campo String privado llamado
appNamey un campo int privado llamadomaxUsers.Un constructor privado que inicialice
appNamea"MyApplication"ymaxUsersa100, y luego imprimaAppConfig 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 imprimaApp: [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
AppConfigy almacénala en una variable llamadaconfig1. Llama adisplaySettings()para mostrar los valores por defecto.Luego, actualiza los ajustes utilizando los valores de entrada llamando a
setAppNameysetMaxUsersenconfig1.Obtén otra instancia y almacénala en
config2. Llama adisplaySettings()enconfig2para demostrar que refleja los cambios realizados a través deconfig1.Finalmente, imprime si ambas referencias apuntan al mismo objeto:
Same instance: [true/false]comparandoconfig1 == 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 staticque contiene la instancia única - Un constructor
privatepara evitar la instanciación directa - Un método
public staticque 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 instanceLa 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
}
}
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
1Fundamentos de OOP
Archivos externosIntroducción a la OOPClases vs. ObjetosLa palabra clave thisMétodosCampos (Atributos)Método constructorSobrecarga de constructoresResumen - Calculadora simple4Herencia
Herencia básica (extends)La palabra clave superSobrescritura de métodos (@Override)Encadenamiento de constructoresLa clase ObjectHerencia simple y multinivelPor qué no hay herencia múltiple de clasesResumen - Jerarquía de empleados7Métodos especiales y la clase Object
Método toString()equals() y hashCode()Método clone()compareTo() y ComparableInterfaz ComparatorResumen - Ordenamiento personalizado2Modificadores de acceso y encapsulamiento
Visión general de los niveles de accesoMétodos Getter y SetterOcultamiento de informaciónLa palabra clave finalResumen - Gestor de cuentas bancarias5Polimorfismo
Fundamentos de sobrecarga de métodosSobrescritura de métodos (tiempo de ejecución)Upcasting y DowncastingEl operador instanceofClases y métodos abstractosResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaAgregación vs. ComposiciónClases internas, anidadas y anónimasEnums y métodos de EnumRecords (Java 16+)Clases selladas (Java 17+)11Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón BuilderPatrón ObserverPatrón Strategy3Propiedades de clase y miembros estáticos
Variables de instancia vs. estáticasMétodos estáticosBloques estáticosConstantes (static final)Repaso - Contador y utilidad6Interfaces y Clases Abstractas
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesMétodos Default y Static en InterfacesClases Abstractas vs InterfacesInterfaces FuncionalesResumen - Sistema de Pagos9Genéricos
Introducción a los genéricosClases genéricasMétodos genéricosParámetros de tipo delimitadosComodines (?, extends, super)Resumen - Contenedor genérico12Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Iterator