viccom/design-patterns

Padrões de projeto comuns com exemplos em Java (Factory, Builder, Strategy, Observer, Decorator, etc.). Use quando o usuário pedir 'implementar padrão', 'usar factory', 'padrão strategy', ou ao projetar componentes extensíveis.

O que é design-patterns?

Esta skill fornece uma referência prática de padrões de projeto (Design Patterns) para desenvolvedores Java, cobrindo os padrões mais utilizados como Builder, Factory, Strategy, Observer, Decorator, Singleton, Adapter e Template Method. Ela é ativada quando o usuário solicita explicitamente a implementação de um padrão específico, ao projetar componentes extensíveis e flexíveis, ao refatorar código rígido ou durante revisões de código que sugerem o uso de padrões. A skill não apenas explica cada padrão com exemplos de código Java moderno (incluindo lambdas, enums e boas práticas como injeção de dependência), mas também oferece alternativas preferenciais usando frameworks como Spring (componentes gerenciados, eventos) e ferramentas como Lombok. O conteúdo é organizado em categorias (Criacionais, Comportamentais e Estruturais) e inclui tabelas de consulta rápida, dicas de quando usar cada padrão, bem como advertências sobre armadilhas comuns, como o uso excessivo de Singletons. A skill suporta qualquer plataforma que execute código Java e é ideal para agentes de IA como Claude Code, Cursor ou Codex, que precisam gerar ou revisar código Java com foco em design orientado a objetos robusto e manutenível.

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

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Skill de Padrões de Projeto

Referência prática de padrões de projeto para Java com exemplos modernos.

Quando Usar

  • O usuário pede para implementar um padrão específico
  • Projetando componentes extensíveis/flexíveis
  • Refatorando estruturas de código rígidas
  • Revisão de código sugere uso de padrões

Referência Rápida: Quando Usar o Quê

ProblemaPadrão
Construção de objeto complexoBuilder
Criar objetos sem especificar a classeFactory
Múltiplos algoritmos, trocar em tempo de execuçãoStrategy
Adicionar comportamento sem alterar a classeDecorator
Notificar múltiplos objetos de mudançasObserver
Garantir instância únicaSingleton
Converter interfaces incompatíveisAdapter
Definir esqueleto de algoritmoTemplate Method

Padrões Criacionais

Builder

Use quando: O objeto tem muitos parâmetros, alguns opcionais.

// ❌ Antipadrão de construtor telescópico
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) { }
    // ... explosão de construtores
}

// ✅ Padrão Builder
public class User {
    private final String name;      // obrigatório
    private final String email;     // obrigatório
    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 {
        // Obrigatórios
        private final String name;
        private final String email;
        // Opcionais com valores padrão
        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("João", "[email protected]")
    .age(30)
    .phone("+551234567890")
    .build();

Com 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

Use quando: Precisar criar objetos sem especificar a classe exata.

// ✅ Padrão 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);
    }
}

// Factory
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 desconhecido: " + type);
        };
    }
}

// Uso
Notification notification = NotificationFactory.create("EMAIL");
notification.send("Olá!");

Com Spring (preferido):

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("Desconhecido: " + type));
    }
}

Singleton

Use quando: Exatamente uma instância é necessária (use com moderação!).

// ✅ Singleton moderno (baseado em enum, thread-safe)
public enum DatabaseConnection {
    INSTANCE;

    private Connection connection;

    DatabaseConnection() {
        // Inicializar conexão
    }

    public Connection getConnection() {
        return connection;
    }
}

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

Com Spring (preferido):

@Component  // Escopo padrão é singleton
public class DatabaseConnection {
    // Spring gerencia a instância única
}

Aviso: Singletons podem ser problemáticos:

  • Difícil de testar (estado global)
  • Dependências ocultas
  • Considere injeção de dependência como alternativa

Padrões Comportamentais

Strategy

Use quando: Múltiplos algoritmos para a mesma operação, necessidade de trocar em tempo de execução.

// ✅ Padrão 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("Pago " + amount + " com cartão " + 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("Pago " + amount + " via 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("Pago " + amount + " para carteira: " + 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"));

// Trocar estratégia em tempo de execução
cart.setPaymentStrategy(new PayPalPayment("[email protected]"));
cart.checkout(new BigDecimal("49.99"));

Com Java 8+ (funcional):

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

// Uso com lambdas
PaymentStrategy creditCard = amount ->
    System.out.println("Pagamento com cartão: " + amount);

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

cart.setPaymentStrategy(creditCard);

Observer

Use quando: Objetos precisam ser notificados de mudanças em outro objeto.

// ✅ Padrão 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) {
        // Processar pedido
        saveOrder(order);

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

// Observadores
public class InventoryService implements OrderObserver {
    @Override
    public void onOrderPlaced(Order order) {
        // Reduzir estoque
        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);
    }
}

// Configuração
OrderService orderService = new OrderService();
orderService.addObserver(new InventoryService());
orderService.addObserver(new EmailNotificationService());
orderService.addObserver(new AnalyticsService());

Com Eventos Spring (preferido):

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

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

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

// Ouvintes (observadores)
@Component
public class InventoryListener {
    @EventListener
    public void handleOrderPlaced(OrderPlacedEvent event) {
        // Reduzir estoque
    }
}

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

    @EventListener
    @Async  // Processamento assíncrono
    public void handleOrderPlacedAsync(OrderPlacedEvent event) {
        // Enviar email de forma assíncrona
    }
}

Template Method

Use quando: Definir esqueleto de algoritmo, deixar que subclasses preencham as etapas.

// ✅ Padrão Template Method
public abstract class DataProcessor {

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

    // Etapas a serem implementadas pelas subclasses
    protected abstract void readData();
    protected abstract void processData();
    protected abstract void writeData();

    // Gancho - substituição opcional
    protected boolean shouldNotify() {
        return true;
    }

    protected void notifyCompletion() {
        System.out.println("Processamento concluído!");
    }
}

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

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

    @Override
    protected void writeData() {
        System.out.println("Escrevendo no banco de dados...");
    }
}

public class ApiDataProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("Buscando da API...");
    }

    @Override
    protected void processData() {
        System.out.println("Transformando resposta da API...");
    }

    @Override
    protected void writeData() {
        System.out.println("Escrevendo em cache...");
    }

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

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

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

Padrões Estruturais

Decorator

Use quando: Adicionar comportamento dinamicamente sem modificar classes existentes.

// ✅ Padrão 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() + ", Leite";
    }

    @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() + ", Açú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() + ", Chantily";
    }

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

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

coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " -> " + coffee.getCost());

coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " -> " + coffee.getCost());

coffee = new WhippedCreamDecorator(coffee);
System.out.println(coffee.getDescription() + " -> " + coffee.getCost());

Adapter

Use quando: Tornar classes incompatíveis trabalharem juntas.

// ✅ Padrão Adapter
// Serviço externo (incompatível)
public class ExternalPaymentService {
    public void processPayment(String currency, double amount) {
        System.out.println("Pagamento externo: " + amount + " " + currency);
    }
}

// Interface esperada
public interface PaymentGateway {
    void pay(BigDecimal amount);
    String getCurrency();
}

// Adaptador
public class ExternalPaymentAdapter implements PaymentGateway {
    private final ExternalPaymentService externalService;
    private static final String CURRENCY = "USD";

    public ExternalPaymentAdapter(ExternalPaymentService externalService) {
        this.externalService = externalService;
    }

    @Override
    public void pay(BigDecimal amount) {
        externalService.processPayment(CURRENCY, amount.doubleValue());
    }

    @Override
    public String getCurrency() {
        return CURRENCY;
    }
}

// Uso
ExternalPaymentService externalService = new ExternalPaymentService();
PaymentGateway gateway = new ExternalPaymentAdapter(externalService);
gateway.pay(new BigDecimal("100.00"));

Anti-Padrões & Boas Práticas

Quando usar padrões

✅ Use padrões quando:

  • Problema de design reconhecido e testado
  • Melhoram a comunicabilidade do código
  • Reduzem acoplamento

❌ Não use padrões quando:

  • Solução mais simples funciona
  • Adiciona complexidade desnecessária
  • Forçar padrão em problema errado

Princípios-chave

  1. Programe para interfaces, não implementações
  2. Prefira composição sobre herança
  3. Padrões resolvem problemas, não os criam
  4. Padrões são guias, não regras absolutas

Práticas modernas do Java

Versão JavaCaracterísticaPadrão afetado
Java 8+Lambdas & StreamsStrategy, Observer
Java 9+Coleções imutáveis (List.of, etc.)Builder pode ser substituído
Java 14+RecordsDTOs, Value Objects
Java 15+Text BlocksStrings multilinha
Java 17+Pattern MatchingVisitor simplificado

*Skill baseada no site Refactoring Guru e experiência prática de desenvolvimento Java.

Individual skills in this repo

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

Habilidades Relacionadas