Dominando el Software: 8 Patrones de Diseño Esenciales para Programadores
Los patrones de diseño son soluciones típicas a problemas comunes en el diseño de software. Aquí tienes ocho patrones de diseño esenciales que todo programador debería conocer, cada uno con una breve descripción y ejemplos prácticos para ilustrar cómo se pueden aplicar en situaciones reales de desarrollo.
1. Singleton
Propósito: Garantizar que una clase tenga solo una instancia y proporcionar un punto de acceso global a ella. Ejemplo: Usar un Singleton para manejar la conexión a una base de datos puede asegurar que las operaciones de lectura y escritura se realicen sobre la misma instancia de conexión, evitando conflictos y sobrecargas innecesarias.
public class Database {
private static Database instance;
private Database() {}
public static Database getInstance() {
if (instance == null) {
instance = new Database();
}
return instance;
}
}
2. Observer
Propósito: Definir una dependencia uno-a-muchos entre objetos para que cuando uno cambie de estado, todos sus dependientes sean notificados automáticamente.
Ejemplo: En una aplicación de comercio electrónico, un objeto Inventory
puede notificar a Display
y OrderManagement
sistemas cuando un artículo es reabastecido.
public class ProductInventory implements Subject {
private List<Observer> observers = new ArrayList<>();
private int stock;
public void setStock(int stock) {
this.stock = stock;
notifyAllObservers();
}
public void attach(Observer observer) {
observers.add(observer);
}
public void notifyAllObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
3. Factory Method
Propósito: Definir una interfaz para crear un objeto, pero dejar que las subclases decidan qué clase instanciar.
Ejemplo: Un framework de UI puede proporcionar una interfaz Button
, pero permitir que las subclases determinen si se crea un WindowsButton
o un MacOSButton
.
public abstract class Dialog {
public void renderWindow() {
Button okButton = createButton();
okButton.render();
}
public abstract Button createButton();
}
public class WindowsDialog extends Dialog {
public Button createButton() {
return new WindowsButton();
}
}
4. Decorator
Propósito: Añadir dinámicamente responsabilidades adicionales a los objetos. Ejemplo: En una aplicación de dibujo, diferentes efectos como bordes o sombras pueden ser añadidos a una figura mediante Decorators.
public interface Shape {
void draw();
}
public class Circle implements Shape {
public void draw() {
System.out.println("Drawing a circle");
}
}
public class RedBorderDecorator extends ShapeDecorator {
public RedBorderDecorator(Shape decoratedShape) {
super(decoratedShape);
}
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape) {
System.out.println("Border Color: Red");
}
}
5. Strategy
Propósito: Permitir que el algoritmo varíe independientemente de los clientes que lo usan. Ejemplo: Un sistema de navegación puede cambiar el algoritmo de cálculo de ruta basado en el tipo de vehículo, condiciones del tráfico, o preferencias del usuario.
public interface RouteStrategy {
void buildRoute(String pointA, String pointB);
}
public class PublicTransportStrategy implements RouteStrategy {
public void buildRoute(String pointA, String pointB) {
System.out.println("Building public transport route");
}
}
6. Adapter
Propósito: Convertir la interfaz de una clase en otra interfaz que los clientes esperan. Ejemplo: Un adaptador puede permitir que datos de sistemas de terceros se integren en una aplicación propia sin modificar el código que espera un formato específico.
public interface Customer {
String getName();
String getEmail();
}
public class ExternalCustomer {
private String name;
private String contactEmail;
public String getName() {
return name;
}
public String getEmail() {
return contactEmail;
}
}
public class CustomerAdapter implements Customer {
private ExternalCustomer externalCustomer;
public CustomerAdapter(ExternalCustomer externalCustomer) {
this.externalCustomer = externalCustomer;
}
public String getName() {
return externalCustomer.getName();
}
public String getEmail() {
return externalCustomer.getEmail();
}
}
7. Command
Propósito: Encapsular una solicitud como un objeto, permitiendo así parametrizar clientes con diferentes solicitudes, hacer cola o llevar un registro de las solicitudes, y soportar operaciones deshacibles. Ejemplo: Una aplicación de edición de texto puede usar comandos para realizar y revertir ediciones, permitiendo así fácilmente implementar funcionalidad “deshacer”.
public interface Command {
void execute();
}
public class CopyCommand implements Command {
private Document document;
public CopyCommand(Document document) {
this.document = document;
}
public void execute() {
document.copy();
}
}
8. Facade
Propósito: Proporcionar una interfaz unificada a un conjunto de interfaces en un subsistema. Facade define una interfaz de nivel más alto que hace el subsistema más fácil de usar. Ejemplo: Un sistema de home automation puede tener un facade que permite controlar luces, termostato, y sistema de seguridad con un solo método.
public class HomeAutomationFacade {
private Light light;
private Thermostat thermostat;
private SecuritySystem securitySystem;
public HomeAutomationFacade(Light light, Thermostat thermostat, SecuritySystem securitySystem) {
this.light = light;
this.thermostat = thermostat;
this.securitySystem = securitySystem;
}
public void awayMode() {
light.turnOff();
thermostat.setTemperature(18);
securitySystem.activate();
}
}
Estos patrones son herramientas poderosas en el arsenal de cualquier desarrollador, y su correcta aplicación puede resultar en software más robusto, flexible y mantenible.