设计模式不是面试题,是日常工具
很多开发者对设计模式的印象停留在面试准备阶段——背一下单例模式的五种写法、说说工厂方法和抽象工厂的区别、画一下观察者模式的类图。面试过了之后,该写面条代码还是写面条代码。
但设计模式真正有价值的地方不在面试,在于解决日常的架构设计问题。特别是在微服务架构中,服务之间的通信、扩展、容错和可观测性,几乎每个问题都能找到对应的设计模式。
本文从《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 个模式本身,而是"识别何时该用什么模式"的思维方式。这种思维方式,才是让代码从"能跑"变成"好维护"的关键。