Patrones de diseño en Java: de la teoría a la práctica
Los patrones de diseño (Design Patterns) son soluciones probadas para problemas típicos en el desarrollo de software. Para un desarrollador de Java, conocer los patrones no es solo una ventaja, sino una necesidad. Permiten escribir código flexible, mantenible y escalable. En este artículo, analizaremos cuatro patrones clave del catálogo GoF (Gang of Four) con ejemplos reales en Java.
¿Por qué son importantes los patrones para un desarrollador de Java?
Java es un lenguaje orientado a objetos fuertemente tipado donde los patrones ayudan a:
- Reducir el acoplamiento del código — las clases dependen de abstracciones, no de implementaciones concretas.
- Aumentar la reutilización — las soluciones ya preparadas se adaptan fácilmente a nuevas tareas.
- Simplificar el trabajo en equipo — los patrones son un lenguaje común para los desarrolladores.
1. Singleton (Instancia única)
El patrón Singleton garantiza que una clase tenga solo una instancia y proporciona un punto de acceso global a ella. En Java, la implementación clásica es la siguiente:
public class DatabaseConnection { private static volatile DatabaseConnection instance; private String connectionString;
private DatabaseConnection(String connectionString) { this.connectionString = connectionString; }
public static DatabaseConnection getInstance(String connectionString) { if (instance == null) { synchronized (DatabaseConnection.class) { if (instance == null) { instance = new DatabaseConnection(connectionString); } } } return instance; }
public void connect() { System.out.println("Connected to: " + connectionString); }}¿Cuándo usar Singleton?
- Gestión de la conexión a la base de datos.
- Registradores (Loggers) y gestores de configuración.
- Grupo de hilos (Thread Pool).
Importante: En las aplicaciones Java modernas, Singleton a menudo se reemplaza por la inyección de dependencias (contenedores DI, Spring).
2. Factory Method (Método de fábrica)
Factory Method define una interfaz para crear un objeto, pero permite que las subclases decidan qué clase instanciar. Veamos un ejemplo de creación de notificaciones:
// Interfaz del productointerface Notification { void send(String message);}
// Productos concretosclass EmailNotification implements Notification { @Override public void send(String message) { System.out.println("Sending Email: " + message); }}
class SMSNotification implements Notification { @Override public void send(String message) { System.out.println("Sending SMS: " + message); }}
// Fábricaabstract class NotificationFactory { public abstract Notification createNotification();
public void notifyUser(String message) { Notification notification = createNotification(); notification.send(message); }}
class EmailNotificationFactory extends NotificationFactory { @Override public Notification createNotification() { return new EmailNotification(); }}
class SMSNotificationFactory extends NotificationFactory { @Override public Notification createNotification() { return new SMSNotification(); }}
// Usopublic class Main { public static void main(String[] args) { NotificationFactory factory = new EmailNotificationFactory(); factory.notifyUser("Hello, User!"); }}Ventajas de Factory Method:
- El código no depende de clases concretas.
- Agregar nuevos tipos de notificaciones no requiere modificar el código existente (principio Open/Closed).
- Simplifica las pruebas unitarias.