CommunityProgramación y desarrollogithub.com

viccom/design-patterns

Patrones de diseño comunes con ejemplos en Java (Factory, Builder, Strategy, Observer, Decorator, etc.). Úsalo cuando el usuario solicite 'implementar patrón', 'usar factory', 'patrón strategy' o al diseñar componentes extensibles.

¿Qué es design-patterns?

Esta habilidad proporciona una referencia práctica de patrones de diseño implementados en Java con ejemplos modernos. Es especialmente útil para desarrolladores que necesitan implementar patrones específicos como Builder, Factory, Strategy, Observer, Decorator y Singleton, optimizando el diseño de componentes extensibles y flexibles. Además, incluye variantes con frameworks populares como Spring y Lombok, y muestra anti-patrones comunes (como el antipatrón de telescoping constructors) para evitarlos. Compatible con cualquier entorno de desarrollo Java (IDE, línea de comandos, agentes de IA como Claude Code) y perfecto para refactorizar código rígido, revisiones de código y diseño de sistemas modulares. La habilidad organiza los patrones en categorías (creacionales, estructurales y de comportamiento) y ofrece tablas de referencia rápida para seleccionar el patrón adecuado según el problema.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/viccom/viccom-agent-skills/tree/HEAD/skills/design-patterns

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Habilidad de Patrones de Diseño

Referencia práctica de patrones de diseño para Java con ejemplos modernos.

Cuándo usar

  • El usuario solicita implementar un patrón específico
  • Diseño de componentes extensibles/flexibles
  • Refactorización de estructuras de código rígidas
  • Revisión de código que sugiere uso de patrones

Referencia rápida: Cuándo usar qué

ProblemaPatrón
Construcción de objetos complejosBuilder
Crear objetos sin especificar la claseFactory
Múltiples algoritmos, intercambiables en tiempo de ejecuciónStrategy
Agregar comportamiento sin modificar la claseDecorator
Notificar a múltiples objetos sobre cambiosObserver
Garantizar una única instanciaSingleton
Convertir interfaces incompatiblesAdapter
Definir un esqueleto de algoritmoTemplate Method

Patrones creacionales

Builder

Úsalo cuando: El objeto tiene muchos parámetros, algunos opcionales.

// ❌ Antipatrón de constructores telescópicos
public class User {
    public User(String name) { }
    public User(String name, String email) { }
    public User(String name, String email, int age) { }
    public User(String name, String email, int age, String phone) { }
    // ... explosión de constructores
}

// ✅ Patrón Builder
public class User {
    private final String name;      // requerido
    private final String email;     // requerido
    private final int age;          // opcional
    private final String phone;     // opcional
    private final String address;   // opcional

    private User(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public static Builder builder(String name, String email) {
        return new Builder(name, email);
    }

    public static class Builder {
        // Requeridos
        private final String name;
        private final String email;
        // Opcionales con valores predeterminados
        private int age = 0;
        private String phone = "";
        private String address = "";

        private Builder(String name, String email) {
            this.name = name;
            this.email = email;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

// Uso
User user = User.builder("Juan", "[email protected]")
    .age(30)
    .phone("+1234567890")
    .build();

Con Lombok:

@Builder
@Getter
public class User {
    private final String name;
    private final String email;
    @Builder.Default private int age = 0;
    private String phone;
}

Factory Method

Úsalo cuando: Necesitas crear objetos sin especificar la clase exacta.

// ✅ Patrón Factory Method
public interface Notification {
    void send(String message);
}

public class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

public class SmsNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

public class PushNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Push: " + message);
    }
}

// Fábrica
public class NotificationFactory {

    public static Notification create(String type) {
        return switch (type.toUpperCase()) {
            case "EMAIL" -> new EmailNotification();
            case "SMS" -> new SmsNotification();
            case "PUSH" -> new PushNotification();
            default -> throw new IllegalArgumentException("Tipo desconocido: " + type);
        };
    }
}

// Uso
Notification notification = NotificationFactory.create("EMAIL");
notification.send("¡Hola!");

Con Spring (recomendado):

public interface NotificationSender {
    void send(String message);
    String getType();
}

@Component
public class EmailSender implements NotificationSender {
    @Override public void send(String message) { /* ... */ }
    @Override public String getType() { return "EMAIL"; }
}

@Component
public class SmsSender implements NotificationSender {
    @Override public void send(String message) { /* ... */ }
    @Override public String getType() { return "SMS"; }
}

@Component
public class NotificationFactory {
    private final Map<String, NotificationSender> senders;

    public NotificationFactory(List<NotificationSender> senderList) {
        this.senders = senderList.stream()
            .collect(Collectors.toMap(
                NotificationSender::getType,
                Function.identity()
            ));
    }

    public NotificationSender getSender(String type) {
        return Optional.ofNullable(senders.get(type))
            .orElseThrow(() -> new IllegalArgumentException("Desconocido: " + type));
    }
}

Singleton

Úsalo cuando: Se necesita exactamente una instancia (¡úsalo con moderación!).

// ✅ Singleton moderno (basado en enum, seguro para hilos)
public enum DatabaseConnection {
    INSTANCE;

    private Connection connection;

    DatabaseConnection() {
        // Inicializar conexión
    }

    public Connection getConnection() {
        return connection;
    }
}

// Uso
Connection conn = DatabaseConnection.INSTANCE.getConnection();

Con Spring (recomendado):

@Component  // El ámbito predeterminado es singleton
public class DatabaseConnection {
    // Spring gestiona la instancia única
}

Advertencia: Los Singletons pueden ser problemáticos:

  • Difíciles de probar (estado global)
  • Dependencias ocultas
  • Considera la inyección de dependencias en su lugar

Patrones de comportamiento

Strategy

Úsalo cuando: Múltiples algoritmos para la misma operación, necesidad de intercambiarlos en tiempo de ejecución.

// ✅ Patrón Strategy
public interface PaymentStrategy {
    void pay(BigDecimal amount);
}

public class CreditCardPayment implements PaymentStrategy {
    private final String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Pagado " + amount + " con tarjeta " + cardNumber);
    }
}

public class PayPalPayment implements PaymentStrategy {
    private final String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Pagado " + amount + " vía PayPal: " + email);
    }
}

public class CryptoPayment implements PaymentStrategy {
    private final String walletAddress;

    public CryptoPayment(String walletAddress) {
        this.walletAddress = walletAddress;
    }

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Pagado " + amount + " a la billetera: " + walletAddress);
    }
}

// Contexto
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(BigDecimal total) {
        paymentStrategy.pay(total);
    }
}

// Uso
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("4111-1111-1111-1111"));
cart.checkout(new BigDecimal("99.99"));

// Cambiar estrategia en tiempo de ejecución
cart.setPaymentStrategy(new PayPalPayment("[email protected]"));
cart.checkout(new BigDecimal("49.99"));

Con Java 8+ (funcional):

// Estrategia como interfaz funcional
@FunctionalInterface
public interface PaymentStrategy {
    void pay(BigDecimal amount);
}

// Uso con lambdas
PaymentStrategy creditCard = amount ->
    System.out.println("Pago con tarjeta: " + amount);

PaymentStrategy paypal = amount ->
    System.out.println("Pago con PayPal: " + amount);

cart.setPaymentStrategy(creditCard);

Observer

Úsalo cuando: Los objetos necesitan ser notificados sobre cambios en otro objeto.

// ✅ Patrón Observer (Java moderno)
public interface OrderObserver {
    void onOrderPlaced(Order order);
}

public class OrderService {
    private final List<OrderObserver> observers = new ArrayList<>();

    public void addObserver(OrderObserver observer) {
        observers.add(observer);
    }

    public void removeObserver(OrderObserver observer) {
        observers.remove(observer);
    }

    public void placeOrder(Order order) {
        // Procesar pedido
        saveOrder(order);

        // Notificar a todos los observadores
        observers.forEach(observer -> observer.onOrderPlaced(order));
    }
}

// Observadores
public class InventoryService implements OrderObserver {
    @Override
    public void onOrderPlaced(Order order) {
        // Reducir inventario
        order.getItems().forEach(item ->
            reduceStock(item.getProductId(), item.getQuantity())
        );
    }
}

public class EmailNotificationService implements OrderObserver {
    @Override
    public void onOrderPlaced(Order order) {
        sendConfirmationEmail(order.getCustomerEmail(), order);
    }
}

public class AnalyticsService implements OrderObserver {
    @Override
    public void onOrderPlaced(Order order) {
        trackOrderEvent(order);
    }
}

// Configuración
OrderService orderService = new OrderService();
orderService.addObserver(new InventoryService());
orderService.addObserver(new EmailNotificationService());
orderService.addObserver(new AnalyticsService());

Con Eventos de Spring (recomendado):

// Evento
public record OrderPlacedEvent(Order order) {}

// Publicador
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;

    public void placeOrder(Order order) {
        saveOrder(order);
        eventPublisher.publishEvent(new OrderPlacedEvent(order));
    }
}

// Oyentes (observadores)
@Component
public class InventoryListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // Reducir inventario
    }
}

@Component
public class EmailListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // Enviar correo
    }

    @EventListener
    @Async  // Procesamiento asíncrono
    public void handleOrderPlacedAsync(OrderPlacedEvent event) {
        // Enviar correo de forma asíncrona
    }
}

Template Method

Úsalo cuando: Definir un esqueleto de algoritmo y dejar que las subclases completen los pasos.

// ✅ Patrón Template Method
public abstract class DataProcessor {

    // Método plantilla - define el algoritmo
    public final void process() {
        readData();
        processData();
        writeData();
        if (shouldNotify()) {
            notifyCompletion();
        }
    }

    // Pasos a implementar por las subclases
    protected abstract void readData();
    protected abstract void processData();
    protected abstract void writeData();

    // Gancho - sobrescritura opcional
    protected boolean shouldNotify() {
        return true;
    }

    protected void notifyCompletion() {
        System.out.println("¡Procesamiento completado!");
    }
}

public class CsvDataProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("Leyendo archivo CSV...");
    }

    @Override
    protected void processData() {
        System.out.println("Procesando datos CSV...");
    }

    @Override
    protected void writeData() {
        System.out.println("Escribiendo en la base de datos...");
    }
}

public class ApiDataProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("Obteniendo datos de la API...");
    }

    @Override
    protected void processData() {
        System.out.println("Transformando respuesta de la API...");
    }

    @Override
    protected void writeData() {
        System.out.println("Escribiendo en la caché...");
    }

    @Override
    protected boolean shouldNotify() {
        return false;  // Sobrescribir gancho
    }
}

// Uso
DataProcessor csvProcessor = new CsvDataProcessor();
csvProcessor.process();

DataProcessor apiProcessor = new ApiDataProcessor();
apiProcessor.process();

Patrones estructurales

Decorator

Úsalo cuando: Agregar comportamiento dinámicamente sin modificar las clases existentes.

// ✅ Patrón Decorator
public interface Coffee {
    String getDescription();
    BigDecimal getCost();
}

public class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Café";
    }

    @Override
    public BigDecimal getCost() {
        return new BigDecimal("2.00");
    }
}

// Decorador base
public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost();
    }
}

// Decoradores concretos
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Leche";
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost().add(new BigDecimal("0.50"));
    }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Azúcar";
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost().add(new BigDecimal("0.20"));
    }
}

public class WhippedCreamDecorator extends CoffeeDecorator {
    public WhippedCreamDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + ", Crema batida";
    }

    @Override
    public BigDecimal getCost() {
        return coffee.getCost().add(new BigDecimal("0.75"));
    }
}

// Uso
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " -> " + coffee.getCost());

// Agregar decoradores dinámicamente
Coffee milkCoffee = new MilkDecorator(coffee);
Coffee milkSugarCoffee = new SugarDecorator(milkCoffee);
Coffee fullyLoaded = new WhippedCreamDecorator(milkSugarCoffee);

System.out.println(fullyLoaded.getDescription() + " -> " + fullyLoaded.getCost());
// Salida: Café, Leche, Azúcar, Crema batida -> 3.45

Con interfaces funcionales (para operaciones simples):

// Función de decorado simple
@FunctionalInterface
public interface CoffeeMaker {
    Coffee make();

    default CoffeeMaker withMilk() {
        return () -> new MilkDecorator(this.make());
    }

    default CoffeeMaker withSugar() {
        return () -> new SugarDecorator(this.make());
    }

    default CoffeeMaker withWhippedCream() {
        return () -> new WhippedCreamDecorator(this.make());
    }
}

// Uso
CoffeeMaker maker = SimpleCoffee::new;
Coffee coffee = maker.withMilk().withSugar().withWhippedCream().make();
System.out.println(coffee.getDescription()); // Café, Leche, Azúcar, Crema batida

Resumen de mejores prácticas

Cuándo usar cada patrón

ContextoPatrónRazón
El constructor es complejoBuilderMejor legibilidad, campos inmutables, parámetros opcionales
La creación es complejaFactoryEncapsula la lógica de creación, desacopla el código del cliente
Comportamiento intercambiableStrategyCumple el Principio Abierto/Cerrado, algoritmos intercambiables en tiempo de ejecución
Agregar características dinámicamenteDecoratorComposición sobre herencia, comportamiento en tiempo de ejecución
Notificaciones uno a muchosObserverBajo acoplamiento, facilita la extensión
Fragmentos de algoritmoTemplate MethodReutiliza la estructura del algoritmo, permite personalización

Anti-patrones a evitar

  • ❌ Constructores telescópicos → Usa Builder
  • ❌ Declaraciones switch masivas para la creación → Usa Factory con registro
  • ❌ Herencia para comportamiento → Usa Strategy/Decorator
  • ❌ Estado global mutable → Cuestiona Singleton, prefiere DI
  • ❌ Llamadas de encadenamiento profundas → Mantén los decoradores simples

Enfoque preferido para Spring

Siempre que sea posible, utiliza las capacidades integradas de Spring:

  • Inyección de dependencias en lugar de Factory manual
  • Ámbito Singleton predeterminado en lugar de Singleton manual
  • Eventos de aplicación en lugar de Observer manual
  • AOP para decoradores transversales
  • @Conditional para selección de estrategias

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills relacionados