mcpField
D
design-patterns

design-patterns

github.com/piomin开发1.4k63 次安装5 天前更新
代码仓库

关于 design-patterns

面向 Java 开发者的常见设计模式速查技能,帮助根据问题选择模式,并提供实现示例。内容覆盖对象创建、行为与结构模式,以及微服务场景。

主要功能

  1. 01按问题选择常见设计模式
  2. 02提供 Java 模式实现示例
  3. 03覆盖创建、行为和结构模式
  4. 04介绍 Outbox、Saga 与 CQRS
  5. 05列出模式选择建议和反模式

适用场景

  1. 01为复杂对象创建选择 Builder 或 Factory
  2. 02为可替换算法应用 Strategy
  3. 03处理数据库更新与事件发布
  4. 04评估微服务的 Saga 或 CQRS 设计

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/piomin/claude-ai-spring-boot --skill design-patterns

安装后在 Claude Code 中输入/design-patterns确认已加载。

详细介绍

设计模式技能

Java 常见设计模式快速参考。

适用场景

  • 用户要求实现特定模式
  • 设计可扩展、灵活的组件
  • 重构僵化的代码

快速参考:何时使用哪种模式

问题模式适用场景
复杂的对象构造Builder参数很多,部分参数可选
无需指定类即可创建对象Factory类型在运行时确定
多种算法,可在运行时切换Strategy行为因上下文而异
不修改类即可添加行为Decorator需要动态组合
在发生更改时通知多个对象Observer一对多依赖
转换不兼容的接口Adapter集成旧代码或第三方代码
原子性地更新数据库并发布事件Outbox微服务中的双写问题
跨服务分布式事务Saga多服务业务流程
分离读写模型CQRS读写比例严重失衡

创建型模式

Builder

问题: 构造函数参数逐渐增多,且有许多可选参数

// ✅ 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", "[email protected]")
    .age(30)
    .build();
Factory

问题: 无需事先知道确切的类即可创建对象

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

行为型模式

Strategy

问题: 同一操作有多种算法,可在运行时选择

// ✅ Strategy pattern
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("Paid " + amount + " with card " + cardNumber);
    }
}

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);
Observer

问题: 状态更改时通知多个对象

// ✅ 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
    }
}

结构型模式

Decorator

问题: 动态添加行为,而不修改类

// ✅ 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);
Adapter

问题: 让不兼容的接口能够协同工作

// ✅ 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");

企业级/微服务模式

Transactional Outbox

问题: 原子性地更新数据库并发布事件(双写问题)

// ✅ Outbox pattern - write event to DB in same transaction, poll and publish later
@Entity
@Table(name = "outbox_events")
public class OutboxEvent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String aggregateType;
    private String aggregateId;
    private String eventType;

    @Column(columnDefinition = "TEXT")
    private String payload;

    private LocalDateTime createdAt;
    private boolean published;

    // getters/setters
}

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final OutboxEventRepository outboxRepository;

    public OrderService(OrderRepository orderRepository, OutboxEventRepository outboxRepository) {
        this.orderRepository = orderRepository;
        this.outboxRepository = outboxRepository;
    }

    @Transactional
    public Order createOrder(OrderRequest request) {
        Order order = new Order();
        order.setStatus(OrderStatus.CREATED);
        order = orderRepository.save(order);

        OutboxEvent event = new OutboxEvent();
        event.setAggregateType("Order");
        event.setAggregateId(order.getId().toString());
        event.setEventType("OrderCreated");
        event.setPayload(toJson(order));
        event.setCreatedAt(LocalDateTime.now());
        event.setPublished(false);
        outboxRepository.save(event);

        return order;
    }
}

// Poller publishes events and marks them as published
@Component
public class OutboxPoller {
    private final OutboxEventRepository outboxRepository;
    private final KafkaTemplate<String, String> kafka;

    public OutboxPoller(OutboxEventRepository outboxRepository,
                        KafkaTemplate<String, String> kafka) {
        this.outboxRepository = outboxRepository;
        this.kafka = kafka;
    }

    @Scheduled(fixedDelay = 1000)
    @Transactional
    public void publishPendingEvents() {
        List<OutboxEvent> events = outboxRepository.findByPublishedFalse();
        for (OutboxEvent event : events) {
            kafka.send(event.getAggregateType(), event.getAggregateId(), event.getPayload());
            event.setPublished(true);
        }
    }
}
Saga(协同式)

问题: 跨多个微服务的分布式事务

// ✅ Choreography-based saga - each service listens and reacts
// Order Service publishes OrderCreated
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;
    private final OrderRepository orderRepository;

    public OrderService(ApplicationEventPublisher eventPublisher, OrderRepository orderRepository) {
        this.eventPublisher = eventPublisher;
        this.orderRepository = orderRepository;
    }

    @Transactional
    public Order createOrder(OrderRequest request) {
        Order order = new Order();
        order.setStatus(OrderStatus.PENDING);
        order = orderRepository.save(order);
        eventPublisher.publishEvent(new OrderCreatedEvent(order.getId(), request.items()));
        return order;
    }

    @Transactional
    public void handlePaymentFailed(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);
    }
}

// Payment Service listens to OrderCreated, publishes PaymentCompleted or PaymentFailed
@Component
public class PaymentEventHandler {
    private final PaymentService paymentService;

    public PaymentEventHandler(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @KafkaListener(topics = "order-events")
    public void handleOrderCreated(OrderCreatedEvent event) {
        try {
            paymentService.charge(event.orderId(), event.amount());
            // Publish PaymentCompleted
        } catch (PaymentException e) {
            // Publish PaymentFailed → triggers compensating action
        }
    }
}

协同式与编排式:

方面协同式编排式
耦合度低——服务对事件作出响应较高——编排器了解所有步骤
复杂度难以追踪完整流程集中管理,更易理解
适用场景步骤少、流程简单步骤多、补偿复杂
CQRS(命令查询职责分离)

问题: 读写模型的优化需求不同

// ✅ CQRS - separate command and query models

// Command side - normalized, validates business rules
@Service
@Transactional
public class OrderCommandService {
    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher eventPublisher;

    public OrderCommandService(OrderRepository orderRepository,
                                ApplicationEventPublisher eventPublisher) {
        this.orderRepository = orderRepository;
        this.eventPublisher = eventPublisher;
    }

    public Long createOrder(CreateOrderCommand command) {
        Order order = new Order();
        order.setCustomerId(command.customerId());
        order.setStatus(OrderStatus.CREATED);
        command.items().forEach(order::addItem);
        order = orderRepository.save(order);
        eventPublisher.publishEvent(new OrderCreatedEvent(order));
        return order.getId();
    }
}

// Query side - denormalized, optimized for reads
@Service
@Transactional(readOnly = true)
public class OrderQueryService {
    private final OrderViewRepository viewRepository;

    public OrderQueryService(OrderViewRepository viewRepository) {
        this.viewRepository = viewRepository;
    }

    public OrderView findById(Long id) {
        return viewRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException("Order not found"));
    }

    public Page<OrderView> findByCustomer(Long customerId, Pageable pageable) {
        return viewRepository.findByCustomerId(customerId, pageable);
    }
}

// Read model - flat, pre-joined
@Entity
@Table(name = "order_views")
public class OrderView {
    @Id
    private Long id;
    private Long customerId;
    private String customerName;
    private String status;
    private BigDecimal totalAmount;
    private int itemCount;
    private LocalDateTime createdAt;

    // getters/setters
}

// Projection updater - keeps query model in sync
@Component
public class OrderViewUpdater {

    private final OrderViewRepository viewRepository;

    public OrderViewUpdater(OrderViewRepository viewRepository) {
        this.viewRepository = viewRepository;
    }

    @EventListener
    public void on(OrderCreatedEvent event) {
        OrderView view = new OrderView();
        view.setId(event.order().getId());
        view.setCustomerId(event.order().getCustomerId());
        view.setStatus(event.order().getStatus().name());
        view.setTotalAmount(event.order().getTotalAmount());
        view.setItemCount(event.order().getItems().size());
        view.setCreatedAt(event.order().getCreatedAt());
        viewRepository.save(view);
    }
}

适用 CQRS 的场景:

  • 读写比例严重失衡(读多写少)
  • 读模型需要与写模型不同的结构(反规范化视图)
  • 读写有不同的扩展需求

不适用场景: 简单的 CRUD 应用——会增加不必要的复杂度。


模式选择指南

场景模式
对象创建很复杂Builder、Factory
需要动态添加功能Decorator
算法有多个实现Strategy
对状态变化作出响应Observer(Spring Events)
集成旧代码Adapter
原子性地更新数据库并发布事件Outbox
跨服务分布式事务Saga
分离读写优化CQRS

应避免的反模式

反模式问题更好的做法
滥用 Singleton全局状态,难以测试依赖注入
到处使用 Factory过度设计类型已知时,直接使用简单的 new
装饰器链过深难以调试使用组合,并保持链条简短
对简单 CRUD 使用 CQRS不必要的复杂度单一模型、标准仓库
Saga 缺少幂等性重复处理幂等消费者 + 去重键

评论

支持 Markdown