Catálogo de Design Patterns: Singleton, Observer, Factory, Adapter y otros
Otros artículos sobre Design Patterns:
- Fundamentos de Design Patterns (GoF)
- Patrones Creacionales (Singleton, Factory, Builder, Prototype, Abstract Factory)
- Descripción General de Design Patterns
Este artículo es un catálogo exhaustivo de los Design Patterns más importantes, incluyendo Singleton, Observer, Factory, Adapter, Iterator, Strategy, Decorator y Template Method con implementaciones prácticas.
En pocas palabras
Los Design Patterns son plantillas de solución probadas para problemas recurrentes en el desarrollo de software. Permiten crear arquitecturas reutilizables, mantenibles y escalables.
Descripción técnica compacta
Los Design Patterns (patrones de diseño) son soluciones documentadas para problemas típicos en el desarrollo de software orientado a objetos. Fueron categorizados por la “Gang of Four” (GoF) y documentan prácticas probadas.
Categorías de patrones GoF:
Patrones Creacionales
- Singleton: Garantiza que solo exista una instancia
- Factory: Creación de objetos sin instanciación directa
- Abstract Factory: Familias de objetos relacionados
- Builder: Construcción paso a paso de objetos complejos
Patrones Estructurales
- Adapter: Adapta interfaces incompatibles
- Decorator: Añade funcionalidad dinámicamente
- Facade: Interfaz simplificada para subsistemas
- Proxy: Representante de objetos
Patrones de Comportamiento
- Observer: Notificación ante cambios de estado
- Strategy: Algoritmos intercambiables
- Iterator: Acceso secuencial a colecciones
- Template Method: Define el esqueleto de algoritmos
Puntos clave para examen
- Singleton: Instancia global, implementación thread-safe
- Observer: Relación Subject-Observer, acoplamiento débil
- Factory: Centraliza creación de objetos, polimorfismo
- Adapter: Adaptación de interfaces, composición vs herencia
- Iterator: Acceso uniforme a contenedores, hasNext/next
- Strategy: Algoritmos intercambiables, Open-Closed Principle
- Decorator: Extiende funcionalidad, dinámicamente
- Template Method: Esqueleto de algoritmo, métodos hook
- Relevancia IHK: Importante para arquitectura y diseño de software
Componentes principales
- Singleton: Una instancia por clase
- Observer: Relación 1:n para notificaciones
- Factory: Creación de objetos sin new
- Adapter: Adaptación de interfaces
- Iterator: Acceso secuencial a colecciones
- Strategy: Algoritmos intercambiables
- Decorator: Extensión dinámica de funcionalidad
- Template Method: Esqueleto de algoritmo con pasos variables
Ejemplos prácticos
1. Patrón Singleton
// Singleton thread-safe con inicialización perezosa
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private final String connectionUrl;
// Constructor privado evita instanciación externa
private DatabaseConnection() {
this.connectionUrl = "jdbc:mysql://localhost:3306/mydb";
// Simulación de establecimiento de conexión
System.out.println("Datenbankverbindung hergestellt");
}
// Double-Checked Locking para thread-safety
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
public void executeQuery(String sql) {
System.out.println("Führe aus: " + sql);
}
public String getConnectionUrl() {
return connectionUrl;
}
}
// Uso
public class SingletonDemo {
public static void main(String[] args) {
DatabaseConnection conn1 = DatabaseConnection.getInstance();
DatabaseConnection conn2 = DatabaseConnection.getInstance();
System.out.println("Gleiches Objekt: " + (conn1 == conn2)); // true
System.out.println("Connection URL: " + conn1.getConnectionUrl());
conn1.executeQuery("SELECT * FROM users");
}
}
2. Patrón Observer
import java.util.*;
// Subject (Observable)
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// Concrete Subject
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
public void setMeasurements(float temperature, float humidity) {
this.temperature = temperature;
this.humidity = humidity;
measurementsChanged();
}
public void measurementsChanged() {
notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity);
}
}
}
// Observer
interface Observer {
void update(float temperature, float humidity);
}
// Concrete Observer
class TemperatureDisplay implements Observer {
private float temperature;
@Override
public void update(float temperature, float humidity) {
this.temperature = temperature;
display();
}
public void display() {
System.out.println("Temperaturanzeige: " + temperature + "°C");
}
}
class HumidityDisplay implements Observer {
private float humidity;
@Override
public void update(float temperature, float humidity) {
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Feuchtigkeitsanzeige: " + humidity + "%");
}
}
// Uso
public class ObserverDemo {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
TemperatureDisplay tempDisplay = new TemperatureDisplay();
HumidityDisplay humidityDisplay = new HumidityDisplay();
// Registrar observers
weatherStation.registerObserver(tempDisplay);
weatherStation.registerObserver(humidityDisplay);
// Cambiar mediciones
weatherStation.setMeasurements(25.5f, 65.0f);
weatherStation.setMeasurements(26.0f, 63.5f);
// Remover observer
weatherStation.removeObserver(humidityDisplay);
weatherStation.setMeasurements(24.8f, 67.2f);
}
}
3. Patrón Factory
// Product Interface
interface Shape {
void draw();
}
// Concrete Products
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Kreis");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Rechteck");
}
}
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Dreieck");
}
}
// Factory
class ShapeFactory {
public Shape createShape(String shapeType) {
if (shapeType == null || shapeType.isEmpty()) {
return null;
}
switch (shapeType.toUpperCase()) {
case "CIRCLE":
return new Circle();
case "RECTANGLE":
return new Rectangle();
case "TRIANGLE":
return new Triangle();
default:
throw new IllegalArgumentException("Unbekannter Shape-Typ: " + shapeType);
}
}
}
// Uso
public class FactoryDemo {
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactory();
// Crear shapes sin operador new
Shape circle = factory.createShape("CIRCLE");
Shape rectangle = factory.createShape("RECTANGLE");
Shape triangle = factory.createShape("TRIANGLE");
// Usar shapes
circle.draw();
rectangle.draw();
triangle.draw();
// Factory con configuración
createShapesFromConfig(factory);
}
private static void createShapesFromConfig(ShapeFactory factory) {
String[] shapes = {"CIRCLE", "RECTANGLE", "CIRCLE", "TRIANGLE"};
for (String shapeType : shapes) {
try {
Shape shape = factory.createShape(shapeType);
shape.draw();
} catch (IllegalArgumentException e) {
System.err.println("Fehler: " + e.getMessage());
}
}
}
}
4. Adapter Pattern
// Target Interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee (interfaz incompatible)
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// Concrete Adaptee
class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Reproduciendo archivo VLC: " + fileName);
}
@Override
public void playMp4(String fileName) {
// No soportado
}
}
class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
// No soportado
}
@Override
public void playMp4(String fileName) {
System.out.println("Reproduciendo archivo MP4: " + fileName);
}
}
// Adapter
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
// Client
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
// Soportar formatos propios
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Reproduciendo archivo MP3: " + fileName);
}
// Otros formatos a través del adaptador
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Formato multimedia inválido: " + audioType);
}
}
}
// Uso
public class AdapterDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3");
audioPlayer.play("mp4", "video.mp4");
audioPlayer.play("vlc", "movie.vlc");
audioPlayer.play("avi", "video.avi");
}
}
5. Iterator Pattern
import java.util.*;
// Aggregate Interface
interface Container {
Iterator getIterator();
}
// Iterator Interface
interface Iterator {
boolean hasNext();
Object next();
}
// Concrete Aggregate
class NameRepository implements Container {
private String[] names = {"Alice", "Bob", "Charlie", "Diana"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator {
private int index;
@Override
public boolean hasNext() {
return index < names.length;
}
@Override
public Object next() {
if (this.hasNext()) {
return names[index++];
}
return null;
}
}
}
// Uso
public class IteratorDemo {
public static void main(String[] args) {
NameRepository namesRepository = new NameRepository();
for (Iterator iterator = namesRepository.getIterator(); iterator.hasNext();) {
String name = (String) iterator.next();
System.out.println("Name: " + name);
}
// Comparación con Iterator estándar de Java
List<String> nameList = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
System.out.println("\nCon Iterator de Java:");
for (Iterator<String> it = nameList.iterator(); it.hasNext();) {
String name = it.next();
System.out.println("Name: " + name);
}
// Enhanced for loop (azúcar sintáctico)
System.out.println("\nCon enhanced for loop:");
for (String name : nameList) {
System.out.println("Name: " + name);
}
}
}
6. Strategy Pattern
// Strategy Interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategies
class CreditCardPayment implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String date;
public CreditCardPayment(String name, String cardNumber, String cvv, String date) {
this.name = name;
this.cardNumber = cardNumber;
this.cvv = cvv;
this.date = date;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € pagados con tarjeta de crédito.");
System.out.println("Titular: " + name);
System.out.println("Número de tarjeta: **** **** **** " + cardNumber.substring(cardNumber.length() - 4));
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
private String password;
public PayPalPayment(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € pagados con PayPal.");
System.out.println("Cuenta PayPal: " + email);
}
}
class BitcoinPayment implements PaymentStrategy {
private String walletAddress;
public BitcoinPayment(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € pagados con Bitcoin.");
System.out.println("Billetera: " + walletAddress.substring(0, 8) + "...");
}
}
// Context
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("No se ha seleccionado método de pago");
}
paymentStrategy.pay(amount);
}
}
// Uso
public class StrategyDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// Diferentes métodos de pago
cart.setPaymentStrategy(new CreditCardPayment(
"Max Mustermann", "1234567890123456", "123", "12/25"));
cart.checkout(100);
System.out.println("---");
cart.setPaymentStrategy(new PayPalPayment("max@example.com", "password123"));
cart.checkout(250);
System.out.println("---");
cart.setPaymentStrategy(new BitcoinPayment("1A2B3C4D5E6F7G8H9I0J"));
cart.checkout(500);
// Selección dinámica de estrategia
dynamicStrategySelection(cart);
}
private static void dynamicStrategySelection(ShoppingCart cart) {
Scanner scanner = new Scanner(System.in);
System.out.println("\nSelecciona método de pago:");
System.out.println("1. Tarjeta de crédito");
System.out.println("2. PayPal");
System.out.println("3. Bitcoin");
int choice = scanner.nextInt();
int amount = 75;
switch (choice) {
case 1:
cart.setPaymentStrategy(new CreditCardPayment(
"Test User", "1111222233334444", "999", "12/24"));
break;
case 2:
cart.setPaymentStrategy(new PayPalPayment("test@example.com", "test123"));
break;
case 3:
cart.setPaymentStrategy(new BitcoinPayment("ABCDEF123456789"));
break;
default:
System.out.println("Selección inválida");
return;
}
cart.checkout(amount);
scanner.close();
}
}
7. Patrón Decorator
// Component Interface
interface Coffee {
String getDescription();
double getCost();
}
// Concrete Component
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Einfacher Kaffee";
}
@Override
public double getCost() {
return 2.0;
}
}
// Abstract Decorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Milch";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Zucker";
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
}
class WhippedCreamDecorator extends CoffeeDecorator {
public WhippedCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Schlagobers";
}
@Override
public double getCost() {
return super.getCost() + 1.0;
}
}
// Verwendung
public class DecoratorDemo {
public static void main(String[] args) {
// Einfacher Kaffee
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost() + "€");
// Mit Milch
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost() + "€");
// Mit Zucker
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost() + "€");
// Mit Schlagobers
coffee = new WhippedCreamDecorator(coffee);
System.out.println(coffee.getDescription() + " kostet " + coffee.getCost() + "€");
// Flexible Kombination
Coffee customCoffee = new SimpleCoffee();
customCoffee = new MilkDecorator(customCoffee);
customCoffee = new SugarDecorator(customCoffee);
customCoffee = new SugarDecorator(customCoffee); // Doppelter Zucker
System.out.println("\nCustom Kaffee:");
System.out.println(customCoffee.getDescription() + " kostet " + customCoffee.getCost() + "€");
}
}
Este patrón te permite agregar funcionalidades de forma dinámica a objetos. En lugar de heredar, envuelves un objeto con decoradores que amplían su comportamiento. En el ejemplo, partimos de un café simple y vamos agregando ingredientes: leche, azúcar, crema batida. Cada decorador suma su precio y descripción sin modificar la clase original.
8. Patrón Template Method
// Abstract Class mit Template Method
abstract class DataProcessor {
// Template Method (final, damit Unterklassen nicht überschreiben können)
public final void processData() {
loadData();
if (validateData()) {
transformData();
saveData();
} else {
handleError();
}
cleanup();
}
// Abstrakte Methoden (müssen von Unterklassen implementiert werden)
protected abstract void loadData();
protected abstract void transformData();
protected abstract void saveData();
// Hook Methods (können überschrieben werden)
protected boolean validateData() {
return true; // Standard-Implementierung
}
protected void handleError() {
System.out.println("Datenvalidierung fehlgeschlagen!");
}
protected void cleanup() {
System.out.println("Aufräumen abgeschlossen");
}
}
// Concrete Implementierung
class CSVDataProcessor extends DataProcessor {
private String data;
@Override
protected void loadData() {
System.out.println("Lade CSV-Daten...");
data = "name,age,city\nAlice,25,Berlin\nBob,30,Hamburg";
}
@Override
protected void transformData() {
System.out.println("Transformiere CSV-Daten...");
// CSV zu JSON konvertieren
data = data.replace("\n", ",").replace("name", "\"name\"")
.replace("age", "\"age\"").replace("city", "\"city\"");
}
@Override
protected void saveData() {
System.out.println("Speichere transformierte Daten...");
System.out.println("Daten: " + data);
}
@Override
protected boolean validateData() {
// CSV-spezifische Validierung
return data != null && data.contains("name") && data.contains("age");
}
}
class XMLDataProcessor extends DataProcessor {
private String data;
@Override
protected void loadData() {
System.out.println("Lade XML-Daten...");
data = "<users><user name='Alice' age='25'/><user name='Bob' age='30'/></users>";
}
@Override
protected void transformData() {
System.out.println("Transformiere XML-Daten...");
// XML zu JSON konvertieren
data = "[{\"name\":\"Alice\",\"age\":25},{\"name\":\"Bob\",\"age\":30}]";
}
@Override
protected void saveData() {
System.out.println("Speichere transformierte Daten...");
System.out.println("Daten: " + data);
}
@Override
protected void handleError() {
System.out.println("XML-Verarbeitungsfehler!");
// Spezielle Fehlerbehandlung für XML
}
}
// Verwendung
public class TemplateMethodDemo {
public static void main(String[] args) {
System.out.println("=== CSV Prozessor ===");
DataProcessor csvProcessor = new CSVDataProcessor();
csvProcessor.processData();
System.out.println("\n=== XML Prozessor ===");
DataProcessor xmlProcessor = new XMLDataProcessor();
xmlProcessor.processData();
// Demonstration des Template Method Patterns
demonstrateTemplateMethod();
}
private static void demonstrateTemplateMethod() {
System.out.println("\n=== Template Method Demonstration ===");
// Der Algorithmus-Ablauf ist immer gleich, nur die Implementierung variiert
DataProcessor[] processors = {
new CSVDataProcessor(),
new XMLDataProcessor()
};
for (DataProcessor processor : processors) {
System.out.println("\nVerarbeite mit " + processor.getClass().getSimpleName() + ":");
processor.processData();
}
}
}
El Template Method define la estructura de un algoritmo en la clase base, pero deja que las subclases implementen los detalles específicos. La clase abstracta DataProcessor define el flujo: cargar datos, validar, transformar, guardar y limpiar. Cada procesador concreto (CSV, XML) implementa sus propios pasos sin cambiar el orden del algoritmo.
Resumen de Categorías de Patrones
Patrones Creacionales
| Patrón | Propósito | Ejemplo |
|---|---|---|
| Singleton | Garantizar una única instancia | Database Connection |
| Factory | Centralizar la creación de objetos | Shape Factory |
| Abstract Factory | Familias de objetos relacionados | GUI Factory |
| Builder | Construir objetos complejos paso a paso | StringBuilder |
Patrones Estructurales
| Patrón | Propósito | Ejemplo |
|---|---|---|
| Adapter | Interfaces incompatibles | Media Adapter |
| Decorator | Añadir funcionalidad dinámicamente | Coffee Decorator |
| Facade | Interfaz simplificada | Home Theater |
| Proxy | Representante | Virtual Proxy |
Patrones de Comportamiento
| Patrón | Propósito | Ejemplo |
|---|---|---|
| Observer | Notificación ante cambios | Weather Station |
| Strategy | Algoritmos intercambiables | Payment Strategy |
| Iterator | Acceso secuencial | Collection Iterator |
| Template Method | Estructura del algoritmo | Data Processor |
Ventajas de los Design Patterns
Reutilización
- Las soluciones probadas pueden adaptarse a nuevos proyectos
- Disminuyen el esfuerzo de desarrollo
- Garantizan coherencia arquitectónica
Mantenibilidad
- Responsabilidades claramente definidas
- Bajo acoplamiento entre componentes
- Cambios y extensiones simplificados
Comunicación
- Lenguaje común entre desarrolladores
- Soluciones documentadas
- Comprensión rápida del código
Calidad
- Soluciones testeadas y validadas
- Menos errores en el código
- Mejor calidad general
Preguntas frecuentes en exámenes
-
¿Cuándo usas el patrón Singleton? Cuando necesitas una única instancia de una clase accesible globalmente (conexión a base de datos, logger).
-
¿Cuál es la diferencia entre Adapter y Decorator? Adapter adapta interfaces incompatibles, mientras que Decorator añade funcionalidad a un objeto existente.
-
Explica el patrón Observer El Subject notifica a los Observers cuando cambia su estado, manteniendo bajo acoplamiento entre componentes.
-
¿Por qué es útil el patrón Template Method? Define la estructura de un algoritmo permitiendo que las subclases implementen pasos específicos.
Referencias principales
- https://refactoring.guru/design-patterns
- https://www.oreilly.com/library/view/head-first-design/0596007124
- https://en.wikipedia.org/wiki/Software_design_pattern
Lecturas recomendadas: Design Patterns
Keine Bücher für Kategorie "design-patterns" gefunden.



