字数:约4200字 | 阅读时间:12分钟
“设计模式不是银弹,但理解模式演进的逻辑,能让你在正确的场景做出正确的选择。”


引言

十年前,我们学习设计模式时,总会先从”四人帮”的23种模式入手。那时候,设计模式更多是一种通用的编程语言底层知识,适用于C++、Java、C#等面向对象语言。

但2026年的今天,云原生、响应式编程、函数式接口、依赖注入容器已成主流。Spring Boot 3.4.x、Java 21的sealed class和record,已经从语法层面原生支持了许多曾经需要”设计模式”才能实现的功能。

设计模式没有消失——它在演进

本文以策略模式为叙事主线,串联装饰器模式、责任链模式、工厂模式,展示设计模式在现代Java生态中的形态变化,以及何时该用、何时该”绕路”。


一、设计模式的本质:封装变化

所有设计模式的核心逻辑只有一条:把变化的部分封装起来,让不变的部分稳定复用

这条原则最早由Gamma等人总结为”对变化的封装”(Encapsulate Varies),后来GoF的《设计模式》将其拆解为23种具体形态。策略模式、装饰器模式、责任链模式、工厂方法——它们解决的都是同一类问题的不同切面。

什么是”变化”

在业务代码中,”变化”通常表现为以下几种形态:

变化类型 表现 典型模式
算法/策略变化 不同算法可替换执行 策略模式
功能叠加变化 运行时动态添加行为 装饰器模式
处理链变化 多个处理器顺序执行 责任链模式
对象创建变化 创建逻辑需要灵活替换 工厂模式

理解”变化”的本质,决定了你是否真的需要设计模式——而不是用模式来”装饰”一段本就简单的代码。


二、策略模式的演进:策略注射(Strategy Injection)

2.1 传统策略模式的问题

传统策略模式的结构很清晰:

1
2
3
4
Context → 持有 → Strategy接口

ConcreteStrategyA
ConcreteStrategyB

一个典型的实现:

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
public interface PayStrategy {
void pay(double amount);
}

public class AlipayStrategy implements PayStrategy {
public void pay(double amount) {
System.out.println("支付宝支付:" + amount);
}
}

public class WechatPayStrategy implements PayStrategy {
public void pay(double amount) {
System.out.println("微信支付:" + amount);
}
}

public class PaymentContext {
private PayStrategy strategy;

public void setStrategy(PayStrategy strategy) {
this.strategy = strategy;
}

public void execute(double amount) {
strategy.pay(amount);
}
}

这段代码有几个问题:

  1. 策略的创建与使用耦合PaymentContext本身不负责创建策略,但在实际业务中,策略往往需要依据业务参数(用户等级、渠道标识、金额区间)来选择
  2. 策略选择逻辑分散:选择哪个策略的if-elseswitch散落在代码各处
  3. 策略对象生命周期管理混乱:策略是每次新建还是复用?没有统一约定

2.2 策略注射:从外部注入到自动装配

Java 21配合Spring Boot 3.4.x,给我们提供了更好的解法——策略注射(Strategy Injection),也叫”策略寻址”(Strategy Resolution)。

核心思路:不再让调用方自己创建策略,而是通过依赖注入框架,根据运行时上下文自动注入合适的策略实现

Spring的Map注入提供了这个能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class PaymentService {
// key是策略的名称(由@Bean方法名或@Qualifier决定)
// Spring自动将所有PayStrategy实现注入到这个Map中
private final Map<String, PayStrategy> strategyMap;

public PaymentService(Map<String, PayStrategy> strategyMap) {
this.strategyMap = strategyMap;
}

public void pay(String channel, double amount) {
PayStrategy strategy = strategyMap.get(channel);
if (strategy == null) {
throw new IllegalArgumentException("未知支付渠道:" + channel);
}
strategy.pay(amount);
}
}

配置层:

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class PayConfig {
@Bean
public PayStrategy alipay() {
return new AlipayStrategy();
}

@Bean
public PayStrategy wechat() {
return new WechatPayStrategy();
}
}

调用方只需传入"alipay""wechat"字符串,Spring自动完成策略寻址。这比传统的if-else清晰得多——选择逻辑和执行逻辑完全分离。

2.3 更进一步:策略的选择由框架完成

更进一步,我们可以让Spring根据条件自动选择策略,而不需要调用方传入任何标识。

利用@ConditionalOnProperty

1
2
3
4
5
@Component
@ConditionalOnProperty(name = "payment.default-channel", havingValue = "alipay")
public class DefaultAlipayStrategy implements PayStrategy {
// 业务实现
}

或者更优雅地使用Spring的ApplicationContext进行动态策略查找:

1
2
3
4
5
6
7
8
@Service
public class DynamicStrategyResolver {
private final ApplicationContext ctx;

public PayStrategy resolve(Class<? extends PayStrategy> type) {
return ctx.getBean(type);
}
}

2.4 Java 21的加持:sealed class让策略选择更安全

Java 21引入的sealed class和pattern matching,让策略模式的类型安全提升了一个台阶:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public sealed interface PayStrategy permits AlipayStrategy, WechatPayStrategy, CreditCardStrategy {
double calculateFee(double amount);
}

public record AlipayStrategy(double discountRate) implements PayStrategy {
public double calculateFee(double amount) {
return amount * (1 - discountRate);
}
}

// 调用时,编译器保证穷举
PayStrategy s = getStrategy();
switch (s) {
case AlipayStrategy a -> System.out.println("支付宝费:" + a.calculateFee(amount));
case WechatPayStrategy w -> System.out.println("微信费率:" + w.calculateFee(amount));
case CreditCardStrategy c -> System.out.println("信用卡费:" + c.calculateFee(amount));
}

sealed接口确保了策略类型的有限性,而pattern matching保证了编译器在编译期就能发现未处理的分支——这是Java 21带给设计模式的语言级进化。


三、装饰器模式在Spring Boot拦截器链的应用

装饰器模式的核心是在运行时动态包装对象,添加额外行为,且不改变原接口

在Spring Boot中,拦截器链(InterceptorChain)就是装饰器模式的现代变体。

3.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
public interface DataService {
String getData();
}

public class BasicDataService implements DataService {
public String getData() { return "原始数据"; }
}

public class LoggingDecorator implements DataService {
private final DataService delegate;
public LoggingDecorator(DataService delegate) { this.delegate = delegate; }
public String getData() {
System.out.println("调用前记录日志");
String result = delegate.getData();
System.out.println("调用后记录日志");
return result;
}
}

public class CacheDecorator implements DataService {
private final DataService delegate;
private final Map<String, String> cache = new HashMap<>();
public CacheDecorator(DataService delegate) { this.delegate = delegate; }
public String getData() {
return cache.computeIfAbsent("key", k -> delegate.getData());
}
}

层层嵌套:new CacheDecorator(new LoggingDecorator(new BasicDataService()))——这就是经典的装饰器链。

3.2 Spring Boot的拦截器链:自动代理的装饰器

Spring Boot 3.4.x中的拦截器链,是装饰器模式在框架层的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class TimingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
req.setAttribute("startTime", System.nanoTime());
return true;
}

@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, Exception ex) {
long elapsed = System.nanoTime() - (long) req.getAttribute("startTime");
System.out.println("请求耗时:" + elapsed + "ns");
}
}

注册到Spring MVC:

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private TimingInterceptor timingInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timingInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/health");
}
}

Spring的HandlerInterceptor本质上就是一个装饰器——它在不改变Controller行为的前提下,为请求处理添加了计时、日志、鉴权等横切关注点。InterceptorChain负责按顺序调用这些拦截器,并在适当位置让原始处理逻辑继续执行。

3.3 拦截器 vs 装饰器:选择哪个

场景 推荐
HTTP请求级别的横切逻辑(日志、计时、鉴权) Spring拦截器
业务方法级别的行为增强(缓存、重试、熔断) 装饰器模式
需要在编译时确定类型关系 装饰器(显式类型)
需要在运行时灵活增删行为 拦截器(通过配置)

四、责任链模式在中间件框架的实践

责任链模式(Chain of Responsibility)将多个处理器串联起来,每个处理器决定处理请求或传递给下一个处理器。

在中间件框架中,这个模式无处不在——Netty的Pipeline、Servlet Filter、以及Spring Security的过滤器链,都是责任链的变体。

4.1 从Servlet Filter到Spring Security过滤器链

最朴素的Servlet Filter实现:

1
2
3
4
5
6
7
8
9
10
11
12
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
if (httpReq.getHeader("Authorization") == null) {
((HttpServletResponse) res).sendError(401, "Unauthorized");
return;
}
chain.doFilter(req, res); // 传递给下一个处理器
}
}

Spring Boot 3.4.x中的Spring Security过滤器链,提供了更强大的责任链抽象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/health").permitAll()
.requestMatchers("/api/**").authenticated()
)
.addFilterBefore(new ApiKeyAuthFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}

Spring Security的过滤器链由多个Filter组成,每个Filter负责自己的处理范围——认证、授权、会话管理、CORS——通过责任链串联,最终到达目标Controller。

4.2 自定义责任链:Spring的@Order和@Primary

在自定义业务场景中,Spring的@Order注解和List注入让我们可以轻松构建可插拔的责任链:

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
public interface OrderHandler {
boolean canHandle(Order order);
void handle(Order order);
int getOrder(); // 处理优先级
}

@Service
public class OrderProcessChain {
private final List<OrderHandler> handlers;

public OrderProcessChain(List<OrderHandler> handlers) {
// Spring自动注入所有实现,并按@Order排序
this.handlers = handlers.stream()
.sorted(Comparator.comparingInt(OrderHandler::getOrder))
.toList();
}

public void process(Order order) {
for (OrderHandler handler : handlers) {
if (handler.canHandle(order)) {
handler.handle(order);
}
}
}
}

这样添加新的处理器只需要实现OrderHandler接口并加上@Order——无需修改调用方代码。责任链的扩展性在Spring的帮助下变得极其轻量。


五、工厂模式与依赖注入的对比

工厂模式和依赖注入解决的问题本质上相同:对象创建与对象使用解耦

5.1 工厂模式的现代价值

在依赖注入容器出现之前,工厂模式是管理对象创建的主要手段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface PaymentFactory {
PayStrategy create(String channel);
}

@Component
public class PaymentFactoryImpl implements PaymentFactory {
@Override
public PayStrategy create(String channel) {
return switch (channel) {
case "alipay" -> new AlipayStrategy();
case "wechat" -> new WechatPayStrategy();
default -> throw new IllegalArgumentException(channel);
};
}
}

工厂模式的价值在于创建逻辑的集中管理——当对象创建涉及复杂条件判断、资源加载或参数验证时,工厂仍然不可替代。

5.2 依赖注入的替代

但Spring的依赖注入已经替代了大部分工厂场景:

1
2
3
4
5
6
7
// 直接注入实现,无需工厂
@Autowired
private AlipayStrategy alipayStrategy;

// 或注入Map,获得所有实现
@Autowired
private Map<String, PayStrategy> strategies;

什么时候仍然需要工厂模式?

  • 创建逻辑涉及条件判断、参数验证、资源分配
  • 需要延迟创建(Lazy Initialization)而非启动时注入
  • 创建逻辑涉及第三方库,无法为其添加@Component注解

Spring的ObjectProviderFactoryBean接口也为此提供了补充能力。

5.3 对比总结

维度 工厂模式 依赖注入
创建控制权 调用方控制 容器控制
适用场景 复杂创建逻辑、延迟创建 标准单例或原型作用域
灵活性 高(可自定义逻辑) 中(受容器约束)
可测试性 好(可注入Mock)
Spring集成 可作为@Bean注册 直接@Autowired

在Spring Boot 3.4.x生态下,依赖注入是默认选择,工厂模式作为补充——当注入解决不了问题时,工厂才是答案。


六、如何判断过度设计

设计模式是工具,不是目标。以下信号说明你可能正在过度设计:

信号一:模式嵌套超过三层

如果你的代码是 new A(new B(new C(new D()))),先问自己——真的需要四个装饰器吗?

信号二:用策略模式替代简单的switch

策略模式的优势在于运行时可替换上下文无关。如果你只有两个固定的分支,且不会在运行时切换,switch语句比接口+多实现的策略模式简洁得多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 过度设计
public double calculateTax(Income income) {
Map<String, TaxStrategy> strategies = ctx.getBeansOfType(TaxStrategy.class);
return strategies.get(income.getCountry()).calculate(income);
}

// 合理的switch
public double calculateTax(Income income) {
return switch (income.getCountry()) {
case "CN" -> income.getAmount() * 0.13;
case "US" -> income.getAmount() * 0.10;
default -> 0;
};
}

信号三:接口只有一个实现

如果PayStrategy只有一个实现类AlipayStrategy,那接口本身就是多余。直接用AlipayStrategy,除非你确定未来会有其他实现。

信号四:为了使用模式而引入模式

“这个问题能不能用责任链?”——如果第一反应是模式名称而不是问题本身,说明你正在被模式反向驱动。

信号五:测试覆盖率依赖模式而非业务

模式应该让代码更容易测试。如果你的测试需要理解一堆设计模式才能写,那模式本身可能成了障碍。

判断标准:简单可读原则(YAGNI + KISS)

Kent Beck的YAGNI(You Aren’t Gonna Need It)和KISS(Keep It Simple, Stupid)原则在这里依然适用:先用最简单的方式实现,等变化的本质暴露了,再用模式重构

设计模式是需求变化的响应,而不是预防性措施。把模式当作”债务”——必要时刻的杠杆,用完记得偿还(重构简化)。


结语

设计模式在2026年的Java生态中,角色没有改变——仍然是封装变化的工具。但实现方式已经大幅进化:

  • 策略模式 → 从手动注入到Spring Map注入,再到sealed class的编译期穷举校验
  • 装饰器模式 → 从手动包装到Spring拦截器链的自动代理
  • 责任链模式 → 从回调嵌套到Spring FilterChain的可配置插拔
  • 工厂模式 → 从手动工厂到依赖注入的自动装配

理解这种演进比记住23种模式的结构更重要。当你能判断”这个问题不需要设计模式”的时候,才是真正掌握了设计模式。


相关技术栈

  • Java 21 (LTS)
  • Spring Boot 3.4.x
  • Spring Security
  • Spring MVC Interceptor

推荐阅读

  • 《设计模式:可复用面向对象软件的基础》— Erich Gamma et al.
  • 《重构》— Martin Fowler
  • 《架构整洁之道》— Robert C. Martin