Design Patterns são como aquelas “gambiarras evoluídas” que deram tão certo que viraram padrão 😂
Eles resolvem problemas comuns de forma organizada e elegante, e já são usados dentro do Spring — mesmo quando você nem percebe.
A ideia aqui não é decorar nomes, mas entender quando o padrão resolve um problema real.
🎯 O que vamos ver
- Strategy — pra eliminar ifs gigantes
- Factory Method — pra desacoplar a criação de objetos
- Observer — pra reagir a eventos sem acoplamento
- Decorator — pra adicionar comportamento sem alterar código existente
🧩 1. Strategy Pattern — o matador de ifs gigantes
🧱 Sem pattern (caótico, mas comum):
@Service
public class DiscountService {
public double calculate(String userType, double price) {
if ("VIP".equals(userType)) {
return price * 0.8;
} else if ("REGULAR".equals(userType)) {
return price * 0.9;
} else if ("GUEST".equals(userType)) {
return price * 0.95;
}
return price;
}
}
❌ Problemas:
- Dificulta manutenção
- Toda vez que um novo tipo é adicionado → mais um
if - Violação do princípio OCP (Open/Closed)
✅ Com Strategy Pattern (limpo e extensível):
public interface DiscountStrategy {
double apply(double price);
}
@Component("VIP")
public class VipDiscount implements DiscountStrategy {
public double apply(double price) { return price * 0.8; }
}
@Component("REGULAR")
public class RegularDiscount implements DiscountStrategy {
public double apply(double price) { return price * 0.9; }
}
@Component("GUEST")
public class GuestDiscount implements DiscountStrategy {
public double apply(double price) { return price * 0.95; }
}
@Service
@RequiredArgsConstructor
public class DiscountService {
private final Map<String, DiscountStrategy> strategies;
public double calculate(String userType, double price) {
return strategies.getOrDefault(userType, p -> p).apply(price);
}
}
💬 Agora, se quiser criar um novo tipo de desconto, é só adicionar uma nova classe.
O código está aberto à extensão e fechado à modificação. ✨
🏭 2. Factory Method — criando objetos do jeito certo
🧱 Sem pattern:
@Service
public class NotificationService {
public void send(String type, String message) {
if ("EMAIL".equals(type)) {
new EmailNotification().send(message);
} else if ("SMS".equals(type)) {
new SmsNotification().send(message);
} else {
throw new IllegalArgumentException("Tipo de notificação inválido");
}
}
}
❌ Problemas:
- Cada tipo novo precisa alterar o método
newespalhado = difícil de testar- Alto acoplamento
✅ Com Factory Method:
public interface Notification {
void send(String message);
}
// Implementações concretas (mantidas conforme já existentes)
public class EmailNotification implements Notification {
@Override
public void send(String message) {
// implementação de envio por email
}
}
public class SmsNotification implements Notification {
@Override
public void send(String message) {
// implementação de envio por SMS
}
}
// Factory Method com switch-case
public final class NotificationFactory {
private NotificationFactory() {}
public static Notification create(String type) {
String t = type == null ? "" : type.trim().toUpperCase();
switch (t) {
case "EMAIL":
return new EmailNotification();
case "SMS":
return new SmsNotification();
default:
throw new IllegalArgumentException("Tipo de notificação desconhecido: " + type);
}
}
}
// Serviço usando a factory
@Service
public class NotificationService {
public void send(String type, String message) {
Notification notification = NotificationFactory.create(type);
notification.send(message);
}
}
💬 Novo tipo de notificação?
Cria uma nova Factory e o sistema já entende. Sem tocar nas classes antigas.
🔔 3. Observer Pattern — reagindo a eventos sem acoplamento
🧱 Sem pattern:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repository;
private final EmailService emailService;
private final AuditService auditService;
public void createUser(User user) {
repository.save(user);
emailService.sendWelcome(user);
auditService.registerCreation(user);
}
}
❌ Problemas:
UserServicefaz mais do que deveria- Dificulta testes e manutenção
- Qualquer nova ação exige mudar o método
✅ Com Observer Pattern (usando eventos do Spring):
// Evento
public record UserCreatedEvent(User user) { }
// Publisher
@Service
@RequiredArgsConstructor
public class UserService {
private final ApplicationEventPublisher publisher;
private final UserRepository repository;
public void createUser(User user) {
repository.save(user);
publisher.publishEvent(new UserCreatedEvent(user));
}
}
// Listeners
@Component
public class WelcomeEmailListener {
@EventListener
public void onUserCreated(UserCreatedEvent event) {
System.out.println("📧 Enviando e-mail para " + event.user().getEmail());
}
}
@Component
public class AuditListener {
@EventListener
public void onUserCreated(UserCreatedEvent event) {
System.out.println("🕵️ Auditoria registrada para " + event.user().getName());
}
}
💬 Agora o UserService só salva o usuário.
Quem quiser reagir ao evento, reage de forma independente. 👌
🎨 4. Decorator Pattern — adicionando comportamento sem tocar no código original
🧱 Sem pattern:
@Service
public class EmailNotifier {
public void send(String message) {
System.out.println("📧 Enviando: " + message);
}
}
Aí alguém diz: “precisamos registrar logs e métricas de envio!”
E o dev vai lá e mete isso dentro da classe 😬
✅ Com Decorator Pattern:
public interface Notifier {
void send(String message);
}
@Component
public class EmailNotifier implements Notifier {
public void send(String message) {
System.out.println("📧 Enviando: " + message);
}
}
@Component
@RequiredArgsConstructor
public class LoggingNotifier implements Notifier {
private final EmailNotifier emailNotifier;
public void send(String message) {
System.out.println("🪵 Log: enviando mensagem...");
emailNotifier.send(message);
System.out.println("✅ Log: envio concluído.");
}
}
💬 Agora o comportamento de log está separado, e pode até ser empilhado com outros decoradores (ex: métricas, retry, etc.).
🧭 Conclusão
- Design Patterns não são fórmulas mágicas, são atalhos elegantes pra problemas repetidos.
- O segredo é usar quando faz sentido, e não aplicar “porque viu num livro”.
- E o melhor: o Spring Boot já usa vários deles pra te ajudar a escrever menos e melhor.
💡 Desafio BoraPraticar
Escolhe um
serviceverboso do teu projeto e tenta aplicar um dos 4 patterns.
Depois mostra o antes e depois pra tua equipe e vê como muda o nível da conversa. 😎