这 5 个设计模式在微服务架构中最好用——《Head First 设计模式》精读笔记

《Head First 设计模式》讲了 23 个模式,但在微服务架构中真正高频使用的只有几个。挑出策略、观察者、工厂方法、代理、装饰器 5 个模式,结合实际代码拆解它们在分布式系统中的真实用法。

设计模式不是面试题,是日常工具

很多开发者对设计模式的印象停留在面试准备阶段——背一下单例模式的五种写法、说说工厂方法和抽象工厂的区别、画一下观察者模式的类图。面试过了之后,该写面条代码还是写面条代码。

但设计模式真正有价值的地方不在面试,在于解决日常的架构设计问题。特别是在微服务架构中,服务之间的通信、扩展、容错和可观测性,几乎每个问题都能找到对应的设计模式。

本文从《Head First 设计模式》这本书出发,挑出在微服务架构中使用频率最高的 5 个模式:策略、观察者、工厂方法、代理、装饰器。不讲理论定义,只讲它们在分布式系统中到底怎么用。

1. 策略模式:让业务规则可插拔

经典定义

定义一组算法,把它们一个个封装起来,并使它们可互换。

微服务中的实际场景

微服务中最常见的策略模式应用场景是支付渠道选择路由策略

支付渠道

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 策略接口
public interface PaymentStrategy {
    PaymentResult pay(Order order, PaymentRequest request);
    boolean supports(String channelCode);
}

// 具体策略:微信支付
@Component
public class WechatPayStrategy implements PaymentStrategy {
    @Override
    public PaymentResult pay(Order order, PaymentRequest request) {
        // 调用微信支付 API
        return wechatPayClient.unifiedOrder(order, request);
    }

    @Override
    public boolean supports(String channelCode) {
        return "WECHAT".equals(channelCode);
    }
}

// 具体策略:支付宝
@Component
public class AlipayStrategy implements PaymentStrategy {
    @Override
    public PaymentResult pay(Order order, PaymentRequest request) {
        return alipayClient.tradePay(order, request);
    }

    @Override
    public boolean supports(String channelCode) {
        return "ALIPAY".equals(channelCode);
    }
}

// 策略上下文(路由)
@Service
public class PaymentService {
    private final List<PaymentStrategy> strategies;

    public PaymentService(List<PaymentStrategy> strategies) {
        this.strategies = strategies;
    }

    public PaymentResult pay(Order order, PaymentRequest request) {
        PaymentStrategy strategy = strategies.stream()
            .filter(s -> s.supports(request.getChannelCode()))
            .findFirst()
            .orElseThrow(() -> new UnsupportedChannelException(request.getChannelCode()));
        return strategy.pay(order, request);
    }
}

为什么这里用策略模式而不是 if-else?

因为支付渠道会不断增加。今天支持微信和支付宝,明天可能加银联、云闪付、数字人民币。每加一个渠道,如果用 if-else 就要改一个巨大的方法;用策略模式只需要新增一个实现类,不改已有代码——符合开闭原则

路由策略

在 API 网关或 Service Mesh 中,路由策略也是策略模式的典型应用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public interface RoutingStrategy {
    ServiceInstance select(List<ServiceInstance> instances, Request request);
}

// 轮询策略
public class RoundRobinStrategy implements RoutingStrategy { ... }

// 权重策略
public class WeightedStrategy implements RoutingStrategy { ... }

// 一致性哈希策略
public class ConsistentHashStrategy implements RoutingStrategy { ... }

// 灰度发布策略
public class CanaryStrategy implements RoutingStrategy { ... }

使用建议

适合用策略模式的场景:

  • 有多种可互换的算法或规则
  • 新增渠道/策略时不想改已有代码
  • 策略的选择逻辑比较复杂(不是简单的 true/false)

不适合的场景:

  • 只有两种选择(直接用 if-else 更简单)
  • 策略之间有大量共享逻辑(考虑模板方法模式)

2. 观察者模式:事件驱动的基石

经典定义

定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖者都会收到通知。

微服务中的实际场景

观察者模式在微服务架构中的变体就是事件驱动架构(EDA)。一个服务发出事件,多个服务订阅并处理。

订单状态变更事件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 事件定义
public class OrderStatusChangedEvent {
    private String orderId;
    private OrderStatus oldStatus;
    private OrderStatus newStatus;
    private LocalDateTime changedAt;
}

// 发布者(订单服务)
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;

    public void updateStatus(String orderId, OrderStatus newStatus) {
        Order order = orderRepository.findById(orderId);
        OrderStatus oldStatus = order.getStatus();

        order.setStatus(newStatus);
        orderRepository.save(order);

        // 发布事件
        eventPublisher.publishEvent(new OrderStatusChangedEvent(
            orderId, oldStatus, newStatus, LocalDateTime.now()));
    }
}

// 订阅者 1:库存服务 - 订单支付后扣库存
@Component
@EventListener
public class InventoryEventHandler {
    @EventListener(condition = "#event.newStatus.name() == 'PAID'")
    public void onOrderPaid(OrderStatusChangedEvent event) {
        inventoryService.deductStock(event.getOrderId());
    }
}

// 订阅者 2:通知服务 - 发送订单状态通知
@Component
@EventListener
public class NotificationEventHandler {
    public void onStatusChanged(OrderStatusChangedEvent event) {
        notificationService.sendOrderStatusNotification(
            event.getOrderId(), event.getNewStatus());
    }
}

// 订阅者 3:数据服务 - 更新数据仓库
@Component
@EventListener
public class DataWarehouseEventHandler {
    public void onStatusChanged(OrderStatusChangedEvent event) {
        dataSyncService.syncOrderStatus(event.getOrderId(), event.getNewStatus());
    }
}

关键区别:同步观察者 vs 异步事件

Spring 的 @EventListener 默认是同步的——发布者和订阅者在同一个线程里执行。在微服务中,这通常不是你想要的。你需要异步处理:

1
2
3
4
5
@Async
@EventListener
public void onStatusChanged(OrderStatusChangedEvent event) {
    // 异步执行,不阻塞主流程
}

更彻底的做法是用消息队列(Kafka、RabbitMQ)替代进程内事件,实现真正的跨服务异步解耦。

使用建议

适合用观察者模式的场景:

  • 一个状态变更需要通知多个不同的下游
  • 下游的数量和类型可能增加
  • 不想让发布者知道有哪些订阅者

需要注意的问题:

  • 事件顺序问题:分布式环境下事件的到达顺序不一定跟发送顺序一致
  • 事件丢失问题:进程内事件在服务重启时会丢失,需要用消息队列保证可靠投递
  • 事件溯源:如果需要回溯"这个订单经历了哪些状态变更",需要把事件持久化

3. 工厂方法:服务实例的统一创建

经典定义

定义创建对象的接口,让子类决定实例化哪个类。

微服务中的实际场景

工厂方法在微服务中最常见的应用是数据库连接工厂客户端工厂

多数据源连接工厂

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 工厂接口
public interface DataSourceFactory {
    DataSource create(DataSourceConfig config);
    String supportsDbType();
}

// 达梦数据源工厂
@Component
public class DmDataSourceFactory implements DataSourceFactory {
    @Override
    public DataSource create(DataSourceConfig config) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("dm.jdbc.driver.DmDriver");
        hikariConfig.setJdbcUrl("jdbc:dm://" + config.getHost() + ":" + config.getPort());
        hikariConfig.setMaximumPoolSize(config.getMaxPoolSize());
        // 达梦特有配置
        hikariConfig.addDataSourceProperty("compatibleMode", config.getCompatibleMode());
        hikariConfig.setConnectionTestQuery("SELECT 1");
        return new HikariDataSource(hikariConfig);
    }

    @Override
    public String supportsDbType() {
        return "DM";
    }
}

// GaussDB 数据源工厂
@Component
public class GaussDBDataSourceFactory implements DataSourceFactory {
    @Override
    public DataSource create(DataSourceConfig config) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName("org.postgresql.Driver");
        hikariConfig.setJdbcUrl("jdbc:postgresql://" + config.getHost() + ":" + config.getPort() + "/" + config.getDbName());
        hikariConfig.setMaximumPoolSize(config.getMaxPoolSize());
        return new HikariDataSource(hikariConfig);
    }

    @Override
    public String supportsDbType() {
        return "GAUSSDB";
    }
}

// 工厂注册中心
@Component
public class DataSourceFactoryRegistry {
    private final Map<String, DataSourceFactory> factories;

    public DataSourceFactoryRegistry(List<DataSourceFactory> factoryList) {
        this.factories = factoryList.stream()
            .collect(Collectors.toMap(DataSourceFactory::supportsDbType, f -> f));
    }

    public DataSource create(String dbType, DataSourceConfig config) {
        DataSourceFactory factory = factories.get(dbType);
        if (factory == null) {
            throw new UnsupportedDbTypeException(dbType);
        }
        return factory.create(config);
    }
}

这个模式在信创项目中特别有用——同一个应用可能需要同时连接 MySQL、达梦和 GaussDB,每种数据库的驱动、连接池参数、超时配置都不一样。用工厂方法封装这些差异,上层代码只关心"给我一个数据源"。

使用建议

适合用工厂方法的场景:

  • 对象的创建逻辑比较复杂(不是简单的 new)
  • 需要根据条件创建不同类型的对象
  • 创建逻辑需要封装和复用

不要滥用的场景:

  • 对象的创建只有一行 new,直接 new 就行,不需要工厂

4. 代理模式:服务调用的增强层

经典定义

为其他对象提供一种代理以控制对这个对象的访问。

微服务中的实际场景

代理模式在微服务中无处不在——API 网关是反向代理,RPC 框架的 Stub 是远程代理,AOP 是动态代理

服务调用的增强代理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 远程服务接口
public interface UserService {
    UserDTO getUser(String userId);
    List<UserDTO> listUsers(UserQuery query);
}

// 代理实现:增加缓存、限流、降级
@Component
public class UserServiceProxy implements UserService {
    private final UserService remoteService;  // Feign Client
    private final Cache<String, UserDTO> cache;
    private final CircuitBreaker circuitBreaker;

    @Override
    public UserDTO getUser(String userId) {
        // 1. 缓存代理
        UserDTO cached = cache.getIfPresent(userId);
        if (cached != null) return cached;

        // 2. 熔断代理
        UserDTO result = circuitBreaker.run(
            () -> remoteService.getUser(userId),
            throwable -> fallbackUser(userId)  // 降级
        );

        // 3. 缓存回填
        cache.put(userId, result);
        return result;
    }

    @Override
    public List<UserDTO> listUsers(UserQuery query) {
        // 列表查询不走缓存,但走熔断
        return circuitBreaker.run(
            () -> remoteService.listUsers(query),
            throwable -> Collections.emptyList()
        );
    }

    private UserDTO fallbackUser(String userId) {
        // 降级:返回一个默认用户对象
        return UserDTO.unknown(userId);
    }
}

这个代理做了三件事:缓存、熔断、降级。每一层都是一个独立的横切关注点,用代理模式把它们叠在一起,业务代码完全不需要感知这些增强逻辑。

API 网关:反向代理

API 网关本质上就是一个大规模的反向代理。它做的事情和上面的代理模式一模一样:

1
2
3
4
5
6
7
客户端 → API 网关(代理)→ 后端服务
                ├── 认证(Authentication)
                ├── 限流(Rate Limiting)
                ├── 日志(Access Log)
                ├── 路由(Routing)
                └── 转换(Request/Response Transform)

使用建议

适合用代理模式的场景:

  • 需要在不修改原始对象的情况下增加功能(缓存、日志、权限、限流)
  • 远程调用的透明封装
  • AOP 场景(Spring 的 @Transactional 就是动态代理)

注意性能开销:

  • 每一层代理都有性能开销(反射、字节码生成)
  • 代理层数过多时可能导致调用栈过深、调试困难

5. 装饰器模式:给能力叠加 buff

经典定义

动态地给一个对象添加一些额外的职责,比继承更灵活。

微服务中的实际场景

装饰器模式和代理模式很像,区别在于:代理是控制访问,装饰器是增强功能。在微服务中,装饰器模式最典型的应用是流式处理和中间件链

HTTP 请求/响应的装饰器链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 基础接口
public interface HttpResponseHandler {
    HttpResponse handle(HttpRequest request);
}

// 基础处理器
public class BaseHandler implements HttpResponseHandler {
    @Override
    public HttpResponse handle(HttpRequest request) {
        // 实际的业务逻辑
        return processBusinessLogic(request);
    }
}

// 装饰器 1:增加压缩
public class GzipDecorator implements HttpResponseHandler {
    private final HttpResponseHandler delegate;

    public GzipDecorator(HttpResponseHandler delegate) {
        this.delegate = delegate;
    }

    @Override
    public HttpResponse handle(HttpRequest request) {
        HttpResponse response = delegate.handle(request);
        if (request.supportsGzip() && response.getBody().length > 1024) {
            response.compressGzip();
        }
        return response;
    }
}

// 装饰器 2:增加缓存头
public class CacheControlDecorator implements HttpResponseHandler {
    private final HttpResponseHandler delegate;
    private final int maxAge;

    public CacheControlDecorator(HttpResponseHandler delegate, int maxAge) {
        this.delegate = delegate;
        this.maxAge = maxAge;
    }

    @Override
    public HttpResponse handle(HttpRequest request) {
        HttpResponse response = delegate.handle(request);
        response.addHeader("Cache-Control", "public, max-age=" + maxAge);
        return response;
    }
}

// 装饰器 3:增加 CORS 头
public class CorsDecorator implements HttpResponseHandler {
    private final HttpResponseHandler delegate;

    @Override
    public HttpResponse handle(HttpRequest request) {
        HttpResponse response = delegate.handle(request);
        response.addHeader("Access-Control-Allow-Origin", "*");
        return response;
    }
}

// 组装装饰器链
HttpResponseHandler handler = new CorsDecorator(
    new CacheControlDecorator(
        new GzipDecorator(
            new BaseHandler()
        ), 3600
    )
);

Servlet Filter 链

Java Web 中的 Servlet Filter 链就是装饰器模式的经典实现。每个 Filter 在请求到达业务代码之前和之后都可以做事情:

1
2
3
4
5
6
7
8
9
// 日志 Filter
public class AccessLogFilter implements Filter {
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
        long start = System.currentTimeMillis();
        chain.doFilter(req, resp);  // 传递到下一个 Filter 或目标
        long elapsed = System.currentTimeMillis() - start;
        log.info("{} {} {}ms", method, uri, elapsed);
    }
}

使用建议

适合用装饰器模式的场景:

  • 需要给对象动态叠加多个功能
  • 功能的组合是灵活的(不是所有功能都要叠加)
  • 不想通过继承产生大量子类

和代理模式的区别:

  • 代理模式:关注的是"能不能访问"(权限、缓存、熔断)
  • 装饰器模式:关注的是"增加什么能力"(压缩、日志、加密)

5 个模式的组合使用

在实际的微服务架构中,这 5 个模式经常组合使用。一个典型的 API 调用链路:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
客户端请求
API 网关(代理模式:认证、限流、路由)
工厂方法(根据路由策略创建对应的服务客户端)
策略模式(选择具体的路由/负载均衡算法)
装饰器链(日志、链路追踪、重试、超时控制)
目标服务处理请求
观察者模式(发出事件通知其他服务)

写在最后

设计模式不是越多越好。一个好的架构不是"用了多少种设计模式",而是"用最少的模式解决了最多的问题"。

几个实操建议:

  • 不要为了用模式而用模式。如果一个 if-else 就能解决问题,不需要策略模式。
  • 关注模式的意图,而不是类图。面试考的是类图画法,实际用的是思维方式。
  • Spring 框架已经帮你实现了很多@EventListener 是观察者,@Cacheable 是代理,@Conditional 是策略。理解底层原理比手写实现更重要。
  • 在代码评审中识别模式。当你发现"这段代码如果加个新类型就要改很多 if-else"时,就是策略模式的信号。

《Head First 设计模式》最值得学的不只是 23 个模式本身,而是"识别何时该用什么模式"的思维方式。这种思维方式,才是让代码从"能跑"变成"好维护"的关键。

广告
广告位预留中 (728x90)