设计模式实战:行为型十一侠——策略、观察者、模板方法、责任链、命令、状态等
设计模式实战:行为型十一侠——策略、观察者、模板方法、责任链、命令、状态等
设计模式实战:行为型十一侠——策略、观察者、模板方法、责任链、命令、状态等是软件工程中解决特定问题的经典方案,它提供了可复用的设计思路和代码结构。
本文深入分析了设计模式实战:行为型十一侠——策略、观察者、模板方法、责任链、命令、状态等的适用场景、优缺点和实现方式,是提升代码质量的重要参考。
定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。策略模式让算法的变化独立于使用算法的客户端。
1.2 不用策略模式的痛点
// 烂代码:满屏 if-else
public double calculatePrice(String type, double price) {
if ("normal".equals(type)) {
return price;
} else if ("vip".equals(type)) {
return price * 0.8;
} else if ("svip".equals(type)) {
return price * 0.6;
} else if ("employee".equals(type)) {
return price * 0.5;
}
return price;
}每加一种用户类型,就要改这个方法。违反开闭原则。
1.3 用策略模式重构
// 策略接口
public interface PriceStrategy {
double calculate(double originalPrice);
String getType();
}
// 普通用户
@Component
public class NormalPriceStrategy implements PriceStrategy {
@Override
public double calculate(double originalPrice) {
return originalPrice;
}
@Override
public String getType() { return "normal"; }
}
// VIP 用户
@Component
public class VipPriceStrategy implements PriceStrategy {
@Override
public double calculate(double originalPrice) {
return originalPrice * 0.8;
}
@Override
public String getType() { return "vip"; }
}
// SVIP 用户
@Component
public class SvipPriceStrategy implements PriceStrategy {
@Override
public double calculate(double originalPrice) {
return originalPrice * 0.6;
}
@Override
public String getType() { return "svip"; }
}用 Spring 自动注入所有策略:
@Service
public class PriceService {
private final Map<String, PriceStrategy> strategyMap;
// Spring 自动注入所有 PriceStrategy 实现
public PriceService(List<PriceStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(PriceStrategy::getType, s -> s));
}
public double calculatePrice(String userType, double price) {
PriceStrategy strategy = strategyMap.get(userType);
if (strategy == null) {
throw new IllegalArgumentException("未知用户类型: " + userType);
}
return strategy.calculate(price);
}
}新增策略只需加一个类,不用改任何现有代码。完美遵循开闭原则。
1.4 避坑指南
- 策略类爆炸:策略太多时考虑用枚举或
Map<Function>简化 - 策略选择逻辑:用工厂或 Map 管理策略,不要在客户端 if-else
- Spring 的 Map 注入:
@Autowired Map<String, Strategy>的 key 是 Bean 名
二、观察者模式(Observer)
2.1 什么是观察者模式
定义对象间的一对多依赖关系,当一个对象状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。俗称"发布-订阅"。
2.2 实际场景:订单状态变更通知
下单后需要:发短信、发邮件、加积分、推送物流。如果把这些逻辑全写在 OrderService 里,代码会越来越臃肿。
// 事件
public class OrderCreatedEvent {
private final String orderId;
private final String userId;
private final BigDecimal amount;
public OrderCreatedEvent(String orderId, String userId, BigDecimal amount) {
this.orderId = orderId;
this.userId = userId;
this.amount = amount;
}
// getters...
}
// 发布者
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder(OrderRequest request) {
// 创建订单逻辑...
String orderId = "ORD" + System.currentTimeMillis();
// 发布事件
eventPublisher.publishEvent(
new OrderCreatedEvent(orderId, request.getUserId(), request.getAmount())
);
}
}
// 观察者1:短信通知
@Component
public class SmsNotificationListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
System.out.println("发送短信: 订单 " + event.getOrderId() + " 创建成功");
}
}
// 观察者2:加积分
@Component
public class PointsListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
int points = event.getAmount().intValue();
System.out.println("给用户 " + event.getUserId() + " 加 " + points + " 积分");
}
}
// 观察者3:发邮件
@Component
public class EmailNotificationListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
System.out.println("发送邮件: 订单 " + event.getOrderId() + " 已确认");
}
}2.3 异步观察者
默认 @EventListener 是同步的。加 @Async 实现异步:
@Component
public class SmsNotificationListener {
@Async
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// 异步执行,不阻塞主流程
System.out.println("异步发送短信: " + event.getOrderId());
}
}2.4 避坑指南
| 坑 | 说明 |
|---|---|
| 事件循环依赖 | A 发事件 -> B 监听后发新事件 -> 触发 A -> 死循环 |
| 同步阻塞 | 默认同步,慢监听器拖垮主流程 -> 加 @Async |
| 事务边界 | @EventListener 在事务提交前执行 -> 用 @TransactionalEventListener |
| 事件太多难追踪 | 建议统一事件命名规范,加日志链路追踪 |
@EventListener vs @TransactionalEventListener:
// 事务提交后才执行,如果事务回滚了就不执行
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) {
// 只有订单创建事务成功提交后才发通知
smsService.send(event.getUserId());
}三、模板方法模式(Template Method)
3.1 什么是模板方法模式
定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法让子类在不改变算法结构的情况下,重新定义算法中的某些步骤。
3.2 实际场景:数据导出
导出 Excel/PDF/CSV 流程类似:查询数据 -> 格式化 -> 写入文件 -> 返回。用模板方法:
// 模板抽象类
public abstract class DataExporter<T> {
// 模板方法:定义流程骨架,final 防止子类修改
public final byte[] export(List<T> data) {
// 1. 数据校验
validate(data);
// 2. 查询补充数据
List<T> enrichedData = enrichData(data);
// 3. 生成内容
byte[] content = generateContent(enrichedData);
// 4. 后置处理
postProcess(content);
return content;
}
// 公共逻辑放在父类
protected void validate(List<T> data) {
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("数据不能为空");
}
}
// 子类实现的扩展点
protected abstract List<T> enrichData(List<T> data);
protected abstract byte[] generateContent(List<T> data);
// 钩子方法,子类可选覆盖
protected void postProcess(byte[] content) {
System.out.println("导出完成, 大小: " + content.length + " bytes");
}
}
// Excel 导出
@Component
public class ExcelExporter extends DataExporter<User> {
@Override
protected List<User> enrichData(List<User> data) {
// 补充部门信息
return data; // 简化
}
@Override
protected byte[] generateContent(List<User> data) {
// 生成 Excel
return "Excel content".getBytes();
}
}
// CSV 导出
@Component
public class CsvExporter extends DataExporter<User> {
@Override
protected List<User> enrichData(List<User> data) {
return data;
}
@Override
protected byte[] generateContent(List<User> data) {
StringBuilder sb = new StringBuilder("id,name,email\n");
for (User u : data) {
sb.append(u.getId()).append(",")
.append(u.getName()).append(",")
.append(u.getEmail()).append("\n");
}
return sb.toString().getBytes();
}
}3.3 Spring 中的模板方法
Spring 大量使用模板方法:
JdbcTemplate:execute()是模板方法,StatementCallback是扩展点RestTemplate:doExecute()是模板方法AbstractApplicationContext.refresh():定义了容器初始化流程,子类可覆盖各步骤HttpMessageConverter:read()/write()模板
// JdbcTemplate 的模板方法
jdbcTemplate.execute((Connection conn) -> {
// 你只需要关心业务逻辑,连接管理、异常处理等模板已搞定
Statement stmt = conn.createStatement();
return stmt.executeQuery("SELECT * FROM user");
});3.4 避坑指南
- 模板方法用 final:防止子类破坏流程结构
- 钩子方法给空实现:子类可选覆盖,不强制
- 别滥用继承:如果只有小逻辑不同,用策略模式 + 函数式接口可能更好
四、责任链模式(Chain of Responsibility)
4.1 什么是责任链模式
将请求沿着处理者链传递,每个处理者决定自己处理还是传给下一个。
4.2 实际场景:请求参数校验
注册用户的校验链:用户名校验 -> 密码校验 -> 邮箱校验 -> 手机号校验:
// 处理者接口
public abstract class ValidationHandler {
private ValidationHandler next;
public ValidationHandler setNext(ValidationHandler next) {
this.next = next;
return next;
}
public ValidationResult handle(RegistrationRequest request) {
ValidationResult result = doValidate(request);
if (!result.isSuccess()) {
return result; // 校验失败,终止链
}
if (next != null) {
return next.handle(request);
}
return ValidationResult.success();
}
protected abstract ValidationResult doValidate(RegistrationRequest request);
}
// 用户名校验
public class UsernameValidator extends ValidationHandler {
@Override
protected ValidationResult doValidate(RegistrationRequest request) {
if (request.getUsername() == null || request.getUsername().length() < 3) {
return ValidationResult.fail("用户名至少3个字符");
}
return ValidationResult.success();
}
}
// 密码校验
public class PasswordValidator extends ValidationHandler {
@Override
protected ValidationResult doValidate(RegistrationRequest request) {
if (request.getPassword() == null || request.getPassword().length() < 8) {
return ValidationResult.fail("密码至少8个字符");
}
return ValidationResult.success();
}
}
// 邮箱校验
public class EmailValidator extends ValidationHandler {
@Override
protected ValidationResult doValidate(RegistrationRequest request) {
if (request.getEmail() == null || !request.getEmail().contains("@")) {
return ValidationResult.fail("邮箱格式不正确");
}
return ValidationResult.success();
}
}组装链:
ValidationHandler chain = new UsernameValidator();
chain.setNext(new PasswordValidator())
.setNext(new EmailValidator());
ValidationResult result = chain.handle(request);4.3 Spring 中的责任链
- Spring Security Filter Chain:请求依次通过 SecurityFilterChain 中的每个 Filter
- Spring Interceptor:HandlerInterceptor 的 preHandle/postHandle/afterCompletion
- MyBatis Interceptor:拦截 SQL 执行,如分页插件
// Spring Interceptor 就是责任链
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler);
void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView mv);
void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex);
}4.4 避坑指南
- 链别太长:链太长影响性能,考虑用 Map 或策略模式替代
- 注意终止条件:忘记终止会导致整条链走完,浪费资源
- 循环引用:A->B->C->A 会死循环
五、命令模式(Command)
5.1 什么是命令模式
将请求封装为对象,从而允许你用不同的请求、队列或日志来参数化客户端。
5.2 实际场景:智能家居遥控器
// 命令接口
public interface Command {
void execute();
void undo();
}
// 接收者:灯
public class Light {
public void on() { System.out.println("灯开了"); }
public void off() { System.out.println("灯关了"); }
}
// 具体命令:开灯
public class LightOnCommand implements Command {
private final Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() { light.on(); }
@Override
public void undo() { light.off(); }
}
// 调用者:遥控器
public class RemoteControl {
private final Map<String, Command> commands = new HashMap<>();
private final Deque<Command> history = new LinkedList<>();
public void setCommand(String slot, Command command) {
commands.put(slot, command);
}
public void pressButton(String slot) {
Command cmd = commands.get(slot);
if (cmd != null) {
cmd.execute();
history.push(cmd);
}
}
public void pressUndo() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}5.3 避坑指南
- 命令模式在 Web 开发中较少直接使用,但 Runnable 本质就是命令模式
- 如果只是简单调用,用函数式接口(
Runnable/Consumer)替代即可
六、状态模式(State)
6.1 什么是状态模式
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
6.2 实际场景:订单状态机
// 状态接口
public interface OrderState {
void pay(OrderContext context);
void ship(OrderContext context);
void deliver(OrderContext context);
void cancel(OrderContext context);
String getName();
}
// 待支付
public class PendingPayState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("支付成功");
context.setState(new PaidState());
}
@Override
public void ship(OrderContext context) {
throw new IllegalStateException("待支付状态不能发货");
}
@Override
public void deliver(OrderContext context) {
throw new IllegalStateException("待支付状态不能确认收货");
}
@Override
public void cancel(OrderContext context) {
System.out.println("订单已取消");
context.setState(new CancelledState());
}
@Override
public String getName() { return "待支付"; }
}
// 已支付
public class PaidState implements OrderState {
@Override
public void pay(OrderContext context) {
throw new IllegalStateException("已支付,请勿重复支付");
}
@Override
public void ship(OrderContext context) {
System.out.println("已发货");
context.setState(new ShippedState());
}
@Override
public void deliver(OrderContext context) {
throw new IllegalStateException("未发货不能确认收货");
}
@Override
public void cancel(OrderContext context) {
System.out.println("已支付订单取消,开始退款");
context.setState(new CancelledState());
}
@Override
public String getName() { return "已支付"; }
}
// 上下文
public class OrderContext {
private OrderState state;
public OrderContext() {
this.state = new PendingPayState();
}
public void setState(OrderState state) {
this.state = state;
}
public void pay() { state.pay(this); }
public void ship() { state.ship(this); }
public void deliver() { state.deliver(this); }
public void cancel() { state.cancel(this); }
public String getStateName() { return state.getName(); }
}使用:
OrderContext order = new OrderContext();
System.out.println(order.getStateName()); // 待支付
order.pay();
System.out.println(order.getStateName()); // 已支付
order.ship();
System.out.println(order.getStateName()); // 已发货6.3 避坑指南
- 状态类可以是单例(无状态),用枚举更好
- Spring Statemachine:复杂状态机推荐用 Spring Statemachine 框架
七、迭代器模式(Iterator)
Java 的 Iterator 接口就是迭代器模式的标准实现:
public interface Iterator<E> {
boolean hasNext();
E next();
}日常开发中很少自己写迭代器,用 Java 的 for-each 和 Stream API 即可。
八、中介者模式(Mediator)
用一个中介对象来封装一系列对象交互。Spring 的 DispatcherServlet 就是中介者,协调各组件处理请求。
九、备忘录模式(Memento)
保存对象某个状态,以便恢复。Java 的 Serializable、Spring 的 @Transactional 回滚机制都有备忘录的影子。
十、访问者模式(Visitor)
在不修改类结构的前提下添加操作。Java 编译器中的 AST 遍历用的就是这个模式。日常开发用得少。
十一、解释器模式(Interpreter)
定义一种语言并解释执行。Spring SpEL 表达式就是解释器模式。
十二、Spring 中的行为型模式汇总
| 模式 | Spring 应用 |
|---|---|
| 策略 | Resource 接口(ClassPathResource、FileSystemResource) |
| 观察者 | ApplicationEvent + @EventListener |
| 模板方法 | JdbcTemplate、RestTemplate、RedisTemplate |
| 责任链 | SecurityFilterChain、HandlerInterceptor |
| 命令 | Runnable 封装的任务 |
| 状态 | Lifecycle 接口(启动/停止状态) |
| 迭代器 | CompositeIterator |
| 中介者 | DispatcherServlet |
| 备忘录 | 事务回滚 |
| 访问者 | BeanDefinitionVisitor |
| 解释器 | SpelExpressionParser |
十三、面试要点
Q1:策略模式怎么消除 if-else?
答:定义策略接口,不同策略实现该接口。用 Map 管理策略,根据 key 获取策略执行。结合 Spring 的依赖注入,新策略加 @Component 即可自动注册。
Q2:观察者模式和发布订阅模式的区别?
答:观察者模式中目标和观察者直接依赖(Subject 持有 Observer 列表);发布订阅模式通过事件总线解耦,发布者和订阅者互不感知。Spring 的 ApplicationEvent 更接近发布订阅模式。
Q3:模板方法模式和策略模式的区别?
答:模板方法用继承(子类覆盖钩子方法),策略用组合(注入不同策略)。模板方法固定流程骨架,策略自由替换算法。模板方法适用于流程固定、步骤可变的场景;策略适用于整体算法可替换的场景。
Q4:责任链模式在 Spring Security 中怎么用的?
答:Spring Security 配置一系列 Filter,请求按顺序通过每个 Filter。每个 Filter 负责一项安全检查(认证、授权、CSRF 等),通过则传给下一个,不通过则拒绝请求。
Q5:@EventListener 和 @TransactionalEventListener 的区别?
答:@EventListener 默认同步执行,且在事务内执行;@TransactionalEventListener 在事务的特定阶段执行(默认 AFTER_COMMIT),如果事务回滚则不执行,适合"事务成功后才发通知"的场景。
总结
行为型模式是日常开发中最实用的模式族:
- 策略:消除 if-else,算法可替换
- 观察者:事件驱动,解耦通知
- 模板方法:固定流程,灵活扩展点
- 责任链:链式处理,灵活组合
- 状态:状态机,行为随状态变
- 命令:请求封装,支持撤销
核心思想就两个:把变化的部分封装起来,让对象间通信更松耦合。Spring 框架把这几种模式用到了极致,多读 Spring 源码是学习行为型模式的最佳途径。