| name | design-patterns |
| description | Common design patterns with Java examples (Factory, Builder, Strategy, Observer, Decorator, etc.). Use when user asks "implement pattern", "use factory", "strategy pattern", or when designing extensible components. |
Design Patterns Skill
Quick reference for common design patterns in Java.
When to Use
- User asks to implement a specific pattern
- Designing extensible/flexible components
- Refactoring rigid code
Quick Reference: When to Use What
| Problem | Pattern | Use When |
|---|
| Complex object construction | Builder | Many parameters, some optional |
| Create objects without specifying class | Factory | Type determined at runtime |
| Multiple algorithms, swap at runtime | Strategy | Behavior varies by context |
| Add behavior without changing class | Decorator | Dynamic composition needed |
| Notify multiple objects of changes | Observer | One-to-many dependency |
| Convert incompatible interfaces | Adapter | Integrate legacy/3rd party code |
Creational Patterns
Builder
Problem: Telescoping constructors, many optional parameters
public class User {
private final String name;
private final String email;
private final int age;
private User(Builder builder) {
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
}
public static Builder builder(String name, String email) {
return new Builder(name, email);
}
public static class Builder {
private final String name;
private final String email;
private int age = 0;
private Builder(String name, String email) {
this.name = name;
this.email = email;
}
public Builder age(int age) {
this.age = age;
return this;
}
public User build() {
return new User(this);
}
}
}
User user = User.builder("John", "john@example.com")
.age(30)
.build();
Factory
Problem: Create objects without knowing exact class upfront
public interface Notification {
void send(String message);
}
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("Unknown: " + type);
};
}
}
@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 get(String type) {
return Optional.ofNullable(senders.get(type))
.orElseThrow(() -> new IllegalArgumentException("Unknown: " + type));
}
}
Behavioral Patterns
Strategy
Problem: Multiple algorithms for same operation, choose at runtime
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
public class CreditCardPayment implements PaymentStrategy {
private final String cardNumber;
@Override
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " with card");
}
}
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(BigDecimal total) {
paymentStrategy.pay(total);
}
}
cart.setPaymentStrategy(new CreditCardPayment("4111..."));
cart.checkout(new BigDecimal("99.99"));
@FunctionalInterface
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
PaymentStrategy creditCard = amount -> System.out.println("Card: " + amount);
cart.setPaymentStrategy(creditCard);
Observer
Problem: Notify multiple objects when state changes
public record OrderPlacedEvent(Order order) {}
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public void placeOrder(Order order) {
saveOrder(order);
eventPublisher.publishEvent(new OrderPlacedEvent(order));
}
}
@Component
public class InventoryListener {
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
}
}
@Component
public class EmailListener {
@EventListener
@Async
public void handleOrderPlaced(OrderPlacedEvent event) {
}
}
Structural Patterns
Decorator
Problem: Add behavior dynamically without modifying class
public interface Coffee {
String getDescription();
BigDecimal getCost();
}
public class SimpleCoffee implements Coffee {
public String getDescription() { return "Coffee"; }
public BigDecimal getCost() { return new BigDecimal("2.00"); }
}
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee coffee;
public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; }
}
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) { super(coffee); }
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public BigDecimal getCost() {
return coffee.getCost().add(new BigDecimal("0.50"));
}
}
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
Adapter
Problem: Make incompatible interfaces work together
public interface MediaPlayer {
void play(String filename);
}
public class LegacyAudioPlayer {
public void playMp3(String filename) { }
}
public class Mp3PlayerAdapter implements MediaPlayer {
private final LegacyAudioPlayer legacyPlayer = new LegacyAudioPlayer();
@Override
public void play(String filename) {
legacyPlayer.playMp3(filename);
}
}
MediaPlayer player = new Mp3PlayerAdapter();
player.play("song.mp3");
Pattern Selection Guide
| Situation | Pattern |
|---|
| Object creation is complex | Builder, Factory |
| Need to add features dynamically | Decorator |
| Multiple implementations of algorithm | Strategy |
| React to state changes | Observer |
| Integrate with legacy code | Adapter |
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Better Approach |
|---|
| Singleton abuse | Global state, hard to test | Dependency Injection |
| Factory everywhere | Over-engineering | Simple new if type known |
| Deep decorator chains | Hard to debug | Composition, keep chains short |