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.
86
83%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Quick reference for common design patterns in Java.
| 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 |
Problem: Telescoping constructors, many optional parameters
// ✅ Builder pattern
public class User {
private final String name; // required
private final String email; // required
private final int age; // optional
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);
}
}
}
// Usage
User user = User.builder("John", "john@example.com")
.age(30)
.build();Problem: Create objects without knowing exact class upfront
// ✅ Factory pattern
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);
};
}
}
// Spring version - preferred
@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));
}
}Problem: Multiple algorithms for same operation, choose at runtime
// ✅ Strategy pattern
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);
}
}
// Usage
cart.setPaymentStrategy(new CreditCardPayment("4111..."));
cart.checkout(new BigDecimal("99.99"));
// Functional variant (Java 8+)
@FunctionalInterface
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
PaymentStrategy creditCard = amount -> System.out.println("Card: " + amount);
cart.setPaymentStrategy(creditCard);Problem: Notify multiple objects when state changes
// ✅ Spring Events (preferred)
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) {
// Reduce inventory
}
}
@Component
public class EmailListener {
@EventListener
@Async
public void handleOrderPlaced(OrderPlacedEvent event) {
// Send email
}
}Problem: Add behavior dynamically without modifying class
// ✅ Decorator pattern
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"));
}
}
// Usage
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);Problem: Make incompatible interfaces work together
// ✅ Adapter pattern
public interface MediaPlayer {
void play(String filename);
}
// Legacy code
public class LegacyAudioPlayer {
public void playMp3(String filename) { /* ... */ }
}
// Adapter
public class Mp3PlayerAdapter implements MediaPlayer {
private final LegacyAudioPlayer legacyPlayer = new LegacyAudioPlayer();
@Override
public void play(String filename) {
legacyPlayer.playMp3(filename);
}
}
// Usage
MediaPlayer player = new Mp3PlayerAdapter();
player.play("song.mp3");| 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-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 |
301ea7a
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.