| name | design-patterns |
| description | Use when applying GoF design patterns, enterprise patterns, or architectural patterns to solve structural or behavioral problems in Java/Spring Boot code. |
Design Patterns Skill
You are an expert in applying design patterns to Java and Spring Boot applications. You recommend appropriate patterns and provide idiomatic implementations.
Creational Patterns
Builder Pattern
public class SearchCriteria {
private final String keyword;
private final LocalDate fromDate;
private final LocalDate toDate;
private final List<String> categories;
private final BigDecimal minPrice;
private final BigDecimal maxPrice;
private final int page;
private final int size;
private SearchCriteria(Builder builder) {
this.keyword = builder.keyword;
this.fromDate = builder.fromDate;
this.toDate = builder.toDate;
this.categories = List.copyOf(builder.categories);
this.minPrice = builder.minPrice;
this.maxPrice = builder.maxPrice;
this.page = builder.page;
this.size = builder.size;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String keyword;
private LocalDate fromDate;
private LocalDate toDate;
private List<String> categories = List.of();
private BigDecimal minPrice;
private BigDecimal maxPrice;
private int page = 0;
private int size = 20;
public Builder keyword(String keyword) { this.keyword = keyword; return this; }
public Builder fromDate(LocalDate fromDate) { this.fromDate = fromDate; return this; }
public Builder toDate(LocalDate toDate) { this.toDate = toDate; return this; }
public Builder categories(List<String> categories) { this.categories = categories; return this; }
public Builder minPrice(BigDecimal minPrice) { this.minPrice = minPrice; return this; }
public Builder maxPrice(BigDecimal maxPrice) { this.maxPrice = maxPrice; return this; }
public Builder page(int page) { this.page = page; return this; }
public Builder size(int size) { this.size = size; return this; }
public SearchCriteria build() {
return new SearchCriteria(this);
}
}
}
Factory Method with Spring
public interface NotificationSender {
void send(Notification notification);
NotificationType getType();
}
@Component
public class EmailNotificationSender implements NotificationSender {
@Override
public void send(Notification notification) {
}
@Override
public NotificationType getType() {
return NotificationType.EMAIL;
}
}
@Component
public class SmsNotificationSender implements NotificationSender {
@Override
public void send(Notification notification) {
}
@Override
public NotificationType getType() {
return NotificationType.SMS;
}
}
@Component
public class NotificationSenderFactory {
private final Map<NotificationType, NotificationSender> senders;
public NotificationSenderFactory(List<NotificationSender> senderList) {
this.senders = senderList.stream()
.collect(Collectors.toMap(NotificationSender::getType, Function.identity()));
}
public NotificationSender getSender(NotificationType type) {
var sender = senders.get(type);
if (sender == null) {
throw new UnsupportedOperationException("No sender for type: " + type);
}
return sender;
}
}
Behavioral Patterns
Strategy Pattern
public interface PricingStrategy {
BigDecimal calculatePrice(Order order);
}
@Component("standardPricing")
public class StandardPricingStrategy implements PricingStrategy {
@Override
public BigDecimal calculatePrice(Order order) {
return order.getItems().stream()
.map(item -> item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
@Component("premiumPricing")
public class PremiumPricingStrategy implements PricingStrategy {
private static final BigDecimal DISCOUNT_RATE = new BigDecimal("0.90");
@Override
public BigDecimal calculatePrice(Order order) {
return order.getItems().stream()
.map(item -> item.getUnitPrice()
.multiply(BigDecimal.valueOf(item.getQuantity()))
.multiply(DISCOUNT_RATE))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
@Service
public class PricingService {
private final Map<String, PricingStrategy> strategies;
public PricingService(Map<String, PricingStrategy> strategies) {
this.strategies = strategies;
}
public BigDecimal calculatePrice(Order order, String customerTier) {
var strategyName = customerTier.toLowerCase() + "Pricing";
var strategy = strategies.get(strategyName);
if (strategy == null) {
throw new IllegalArgumentException("Unknown pricing tier: " + customerTier);
}
return strategy.calculatePrice(order);
}
}
Template Method
public abstract class AbstractDataImporter<T> {
private static final Logger log = LoggerFactory.getLogger(AbstractDataImporter.class);
@Transactional
public ImportResult importData(InputStream source) {
log.info("Starting import: {}", getImportName());
var rawRecords = parseSource(source);
var validRecords = rawRecords.stream()
.filter(this::validate)
.toList();
var saved = saveAll(validRecords);
var result = new ImportResult(rawRecords.size(), validRecords.size(), saved.size());
log.info("Import complete: {}", result);
return result;
}
protected abstract String getImportName();
protected abstract List<T> parseSource(InputStream source);
protected abstract boolean validate(T record);
protected abstract List<T> saveAll(List<T> records);
}
@Service
public class EmployeeDataImporter extends AbstractDataImporter<Employee> {
private final EmployeeRepository employeeRepository;
public EmployeeDataImporter(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Override
protected String getImportName() { return "Employee Import"; }
@Override
protected List<Employee> parseSource(InputStream source) {
return List.of();
}
@Override
protected boolean validate(Employee employee) {
return employee.getName() != null && employee.getEmail() != null;
}
@Override
protected List<Employee> saveAll(List<Employee> records) {
return employeeRepository.saveAll(records);
}
}
Observer with Spring Events
public record OrderCreatedEvent(Long orderId, Long customerId, BigDecimal total) {}
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final ApplicationEventPublisher eventPublisher;
public OrderService(OrderRepository orderRepository,
ApplicationEventPublisher eventPublisher) {
this.orderRepository = orderRepository;
this.eventPublisher = eventPublisher;
}
@Transactional
public Order createOrder(CreateOrderRequest request) {
var order = orderFactory.create(request);
var saved = orderRepository.save(order);
eventPublisher.publishEvent(
new OrderCreatedEvent(saved.getId(), saved.getCustomerId(), saved.getTotal()));
return saved;
}
}
@Component
public class OrderNotificationListener {
private static final Logger log = LoggerFactory.getLogger(OrderNotificationListener.class);
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) {
log.info("Sending notification for order: {}", event.orderId());
}
}
@Component
public class InventoryUpdateListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async
public void onOrderCreated(OrderCreatedEvent event) {
}
}
Structural Patterns
Decorator Pattern
public interface OrderPriceCalculator {
BigDecimal calculate(Order order);
}
public class BaseOrderPriceCalculator implements OrderPriceCalculator {
@Override
public BigDecimal calculate(Order order) {
return order.getItems().stream()
.map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
public class TaxDecorator implements OrderPriceCalculator {
private final OrderPriceCalculator delegate;
private final BigDecimal taxRate;
public TaxDecorator(OrderPriceCalculator delegate, BigDecimal taxRate) {
this.delegate = delegate;
this.taxRate = taxRate;
}
@Override
public BigDecimal calculate(Order order) {
var basePrice = delegate.calculate(order);
return basePrice.add(basePrice.multiply(taxRate));
}
}
public class DiscountDecorator implements OrderPriceCalculator {
private final OrderPriceCalculator delegate;
private final BigDecimal discountRate;
public DiscountDecorator(OrderPriceCalculator delegate, BigDecimal discountRate) {
this.delegate = delegate;
this.discountRate = discountRate;
}
@Override
public BigDecimal calculate(Order order) {
var basePrice = delegate.calculate(order);
return basePrice.subtract(basePrice.multiply(discountRate));
}
}
@Configuration
public class PricingConfig {
@Bean
public OrderPriceCalculator orderPriceCalculator() {
return new TaxDecorator(
new DiscountDecorator(
new BaseOrderPriceCalculator(),
new BigDecimal("0.10")),
new BigDecimal("0.21"));
}
}
When to Use Which Pattern
| Problem | Pattern |
|---|
| Complex object construction | Builder |
| Multiple implementations by type | Factory + Strategy |
| Cross-cutting behavior steps | Template Method |
| Reacting to state changes | Observer (Events) |
| Adding behavior without subclassing | Decorator |
| Simplifying complex subsystems | Facade |