关于 design-patterns
面向 Java 开发者的常见设计模式速查技能,帮助根据问题选择模式,并提供实现示例。内容覆盖对象创建、行为与结构模式,以及微服务场景。
主要功能
- 01按问题选择常见设计模式
- 02提供 Java 模式实现示例
- 03覆盖创建、行为和结构模式
- 04介绍 Outbox、Saga 与 CQRS
- 05列出模式选择建议和反模式
适用场景
- 01为复杂对象创建选择 Builder 或 Factory
- 02为可替换算法应用 Strategy
- 03处理数据库更新与事件发布
- 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