SpringBoot中设计模式组合使用示例-策略、模板、观察者、门面、工厂、单例。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
一、策略模式(Strategy Pattern)
概念
定义一组算法/行为,将每个算法封装成独立的处理单元,使它们可以互相替换。客户端根据条件选择使用哪个策略。
解决的问题
避免大量的 if-else 或 switch-case 散落在业务逻辑中,新增策略时不需要修改已有代码。
项目中的体现
根据物流节点编码路由到不同处理方法:
java
switch (OpCodeEnums.valueOfCode(paramsDto.getOpCode())) {
case a: this.aHandle(...); break;
case b: this.bHandle(...); break;
case c: this.cHandle(...); break;
// ...
}
每个 case 分支对应一个独立的处理策略。新增事件只需添加一个 case 和对应方法。
通用代码示例
java
/**
* 策略接口:定义统一的处理行为.
*/
public interface PaymentStrategy {
/** 执行支付. */
PayResult pay(PayRequest request);
/** 是否支持该支付方式. */
boolean support(String payType);
}
/**
* 具体策略:微信支付.
*/
@Component
public class WechatPayStrategy implements PaymentStrategy {
@Override
public PayResult pay(PayRequest request) {
// 调用微信支付 API
return new PayResult(true, "微信支付成功");
}
@Override
public boolean support(String payType) {
return "WECHAT".equals(payType);
}
}
/**
* 具体策略:支付宝支付.
*/
@Component
public class AlipayStrategy implements PaymentStrategy {
@Override
public PayResult pay(PayRequest request) {
// 调用支付宝 API
return new PayResult(true, "支付宝支付成功");
}
@Override
public boolean support(String payType) {
return "ALIPAY".equals(payType);
}
}
/**
* 策略上下文:根据条件选择策略并执行.
* Spring 自动注入所有 PaymentStrategy 实现.
*/
@Service
public class PaymentService {
@Resource
private List<PaymentStrategy> strategies;
public PayResult pay(String payType, PayRequest request) {
PaymentStrategy strategy = strategies.stream()
.filter(s -> s.support(payType))
.findFirst()
.orElseThrow(() -> new RuntimeException("不支持的支付方式: " + payType));
return strategy.pay(request);
}
}
类图
┌────────────────────┐
│ PaymentService │ (上下文,选择策略)
│ - strategies │
│ + pay() │
└────────┬───────────┘
│ 持有
▼
┌────────────────────┐
│ <<interface>> │
│ PaymentStrategy │
│ + pay() │
│ + support() │
└────────┬───────────┘
│ 实现
┌────┴────┐
▼ ▼
┌────────┐ ┌────────┐
│Wechat │ │Alipay │
│Strategy│ │Strategy│
└────────┘ └────────┘
二、模板方法模式(Template Method Pattern)
概念
在父类中定义一个算法的骨架(步骤顺序),将某些步骤的具体实现延迟到子类或通过参数差异化。整体流程不变,只是个别步骤的行为不同。
解决的问题
多个流程整体结构相同,只有部分细节不同。避免重复代码,把共同逻辑抽到一处。
项目中的体现
方法是一个固定模板,不同事件(a/b/c)走同一套流程:
① 判断是否指定类型订单 → 所有事件都一样
② 构造 DTO → event 字段不同
③ 写日志表 → 所有事件都一样
④ 注册事务回调发MQ → 所有事件都一样
⑤ 调用标签推送 → 根据 event 设置不同标签
调用方只需传入不同的 event 参数:
java
aaService.interceptNodeTobb(data, recordItem, "a");
aasService.interceptNodeTobb(data, recordItem, "b");
aasService.interceptNodeTobb(data, recordItem, "c");
通用代码示例
java
/**
* 抽象模板类:定义导出流程骨架.
*/
public abstract class AbstractExportTemplate {
/**
* 模板方法:固定的执行步骤.
* final 防止子类覆盖整体流程.
*/
public final void export(ExportRequest request) {
// 步骤1:校验参数(通用)
validate(request);
// 步骤2:查询数据(子类实现)
List<Object> data = queryData(request);
// 步骤3:转换格式(子类实现)
byte[] content = convertToFile(data, request.getFileType());
// 步骤4:上传文件(通用)
upload(content, request.getFileName());
// 步骤5:发送通知(通用)
notify(request.getUserId(), request.getFileName());
}
private void validate(ExportRequest request) {
if (request.getUserId() == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
}
/** 子类实现:查询数据. */
protected abstract List<Object> queryData(ExportRequest request);
/** 子类实现:转换为文件内容. */
protected abstract byte[] convertToFile(List<Object> data, String fileType);
private void upload(byte[] content, String fileName) {
// 上传到 OSS
}
private void notify(Integer userId, String fileName) {
// 发送消息通知用户
}
}
/**
* 具体实现:订单导出.
*/
@Service
public class OrderExportService extends AbstractExportTemplate {
@Override
protected List<Object> queryData(ExportRequest request) {
return orderRepository.findByCondition(request.getCondition());
}
@Override
protected byte[] convertToFile(List<Object> data, String fileType) {
if ("excel".equals(fileType)) {
return ExcelUtil.toExcel(data);
}
return CsvUtil.toCsv(data);
}
}
/**
* 具体实现:库存导出.
*/
@Service
public class StockExportService extends AbstractExportTemplate {
@Override
protected List<Object> queryData(ExportRequest request) {
return stockRepository.findByWarehouseId(request.getWarehouseId());
}
@Override
protected byte[] convertToFile(List<Object> data, String fileType) {
return ExcelUtil.toExcel(data);
}
}
类图
┌──────────────────────────────┐
│ AbstractExportTemplate │
│ │
│ + export() [final, 模板方法] │
│ - validate() │
│ # queryData() [abstract] │
│ # convertToFile() [abstract] │
│ - upload() │
│ - notify() │
└──────────────┬───────────────┘
│ 继承
┌──────┴──────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│OrderExport │ │StockExport │
│Service │ │Service │
└──────────────┘ └──────────────┘
三、观察者模式(Observer Pattern)
概念
定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都得到通知并自动更新。
解决的问题
一件事发生后需要触发多个后续动作,但不想把这些动作硬编码在主流程中。
项目中的体现
Spring 的 TransactionSynchronizationManager 就是观察者模式的应用:
java
// 事务是被观察的"主题"
// AfterTransactionActionCollector 是"观察者"
// 事务提交这个"事件"触发观察者执行
AfterTransactionActionCollector collector = new AfterTransactionActionCollector();
TransactionSynchronizationManager.registerSynchronization(collector);
collector.addCommitSyncAction(() -> sendJstMessageMqSender.sendJstMessageMq(recordLog.getId()));
事务提交后,所有注册的同步回调自动被触发执行。
通用代码示例
方式一:Spring 事件机制
java
/**
* 事件定义:订单创建事件.
*/
public class OrderCreatedEvent extends ApplicationEvent {
private final String orderCode;
private final Integer userId;
public OrderCreatedEvent(Object source, String orderCode, Integer userId) {
super(source);
this.orderCode = orderCode;
this.userId = userId;
}
public String getOrderCode() { return orderCode; }
public Integer getUserId() { return userId; }
}
/**
* 事件发布者:订单服务.
* 只负责发布事件,不关心谁在监听.
*/
@Service
public class OrderService {
@Resource
private ApplicationEventPublisher eventPublisher;
@Transactional
public void createOrder(OrderDto dto) {
// 核心业务:创建订单
Order order = orderRepository.save(buildOrder(dto));
// 发布事件(不关心后续处理)
eventPublisher.publishEvent(
new OrderCreatedEvent(this, order.getOrderCode(), dto.getUserId()));
}
}
/**
* 观察者1:发送短信通知.
*/
@Component
public class SmsNotificationListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
smsService.send(event.getUserId(), "您的订单已创建:" + event.getOrderCode());
}
}
/**
* 观察者2:初始化物流信息.
*/
@Component
public class LogisticsInitListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
logisticsService.initLogisticsRecord(event.getOrderCode());
}
}
/**
* 观察者3:异步更新统计报表.
*/
@Component
public class ReportListener {
@Async
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
reportService.incrementDailyCount();
}
}
方式二:事务提交后回调
java
/**
* 事务提交后触发动作.
* 确保数据库操作成功后才执行副作用(发MQ、调接口等).
*/
@Service
public class InventoryService {
public void deductStock(Integer skuId, Integer quantity) {
// 扣减库存(数据库操作)
stockRepository.deduct(skuId, quantity);
// 注册事务提交后的回调
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
// 事务成功后才通知下游
mqSender.sendStockChanged(skuId);
cacheService.invalidate("stock:" + skuId);
}
@Override
public void afterCompletion(int status) {
if (status == STATUS_ROLLED_BACK) {
log.warn("事务回滚,不发送通知");
}
}
}
);
}
}
类图
┌───────────────────────┐
│ ApplicationEvent │ (事件)
│ Publisher │
│ + publishEvent() │
└───────────┬───────────┘
│ 发布
▼
┌───────────────────────┐ ┌─────────────────┐
│ OrderCreatedEvent │──────→│ SmsListener │ 观察者1
└───────────────────────┘ 通知 ├─────────────────┤
────→│ LogisticsListener│ 观察者2
────→│ ReportListener │ 观察者3
└─────────────────┘
四、门面模式(Facade Pattern)
概念
为复杂子系统提供一个简化的统一接口。调用方不需要了解子系统内部的复杂性,只需通过门面接口完成操作。
解决的问题
子系统内部有多个类、多个步骤协作,直接暴露给调用方会导致耦合度高、使用复杂。
项目中的体现
aaService 接口就是门面------封装了与 bb 交互的所有复杂逻辑:
java
// 调用方一行代码搞定
aaService.interceptNodeTobb(data, recordItem, "a");
内部实际做了 5 件事:
- 查询是否 指定类型订单(调远程 Feign)
- 构造 DTO
- 写数据库日志表
- 注册事务同步回调
- 发 MQ
调用方无需知道这些细节。
通用代码示例
java
/**
* 门面接口:对外暴露的简化接口.
*/
public interface OrderFacade {
/** 一键下单(内部协调多个子系统). */
OrderResult placeOrder(PlaceOrderRequest request);
}
/**
* 门面实现:协调多个子系统完成复杂操作.
*/
@Service
@Slf4j
public class OrderFacadeImpl implements OrderFacade {
@Resource private InventoryService inventoryService; // 库存子系统
@Resource private PaymentService paymentService; // 支付子系统
@Resource private LogisticsService logisticsService; // 物流子系统
@Resource private NotificationService notificationService; // 通知子系统
@Override
@Transactional(rollbackFor = Exception.class)
public OrderResult placeOrder(PlaceOrderRequest request) {
// 1. 检查库存
boolean hasStock = inventoryService.checkAndLock(
request.getSkuId(), request.getQuantity());
if (!hasStock) {
return OrderResult.fail("库存不足");
}
// 2. 创建支付单
String payNo = paymentService.createPayOrder(
request.getUserId(), request.getAmount());
// 3. 预分配物流
logisticsService.preAllocate(request.getAddress());
// 4. 发送通知
notificationService.sendOrderCreated(request.getUserId());
return OrderResult.success(payNo);
}
}
/**
* 调用方:只需面对门面接口,不关心内部有哪些子系统.
*/
@RestController
public class OrderController {
@Resource
private OrderFacade orderFacade;
@PostMapping("/order/place")
public Result<OrderResult> placeOrder(@RequestBody PlaceOrderRequest request) {
return Result.success(orderFacade.placeOrder(request));
}
}
类图
┌────────────┐ ┌─────────────────────────────────────┐
│ Controller │ │ OrderFacade (门面) │
│ │─────────→│ │
└────────────┘ │ 内部协调: │
│ ┌─────────────┐ ┌───────────────┐ │
│ │ Inventory │ │ Payment │ │
│ │ Service │ │ Service │ │
│ └─────────────┘ └───────────────┘ │
│ ┌─────────────┐ ┌───────────────┐ │
│ │ Logistics │ │ Notification │ │
│ │ Service │ │ Service │ │
│ └─────────────┘ └───────────────┘ │
└─────────────────────────────────────┘
五、工厂模式(Factory Pattern)
概念
将对象的创建逻辑封装到工厂类中,调用方不直接 new 对象,而是通过工厂获取。
解决的问题
对象创建逻辑复杂,或需要根据条件创建不同类型的对象。
项目中的体现
Feign 的 FallbackFactory 就是工厂模式------根据异常类型创建不同的降级实例:
java
@FeignClient(fallbackFactory = aaaOrderFallbackFactory.class)
public interface aaaOrderFeign { ... }
FallbackFactory 接口:
java
public interface FallbackFactory<T> {
T create(Throwable cause); // 工厂方法:根据异常创建降级实例
}
通用代码示例
java
/**
* 产品接口:消息发送器.
*/
public interface MessageSender {
void send(String target, String content);
}
/**
* 具体产品:短信发送.
*/
@Component("SMS")
public class SmsSender implements MessageSender {
@Override
public void send(String target, String content) {
// 调用短信平台 API
}
}
/**
* 具体产品:邮件发送.
*/
@Component("EMAIL")
public class EmailSender implements MessageSender {
@Override
public void send(String target, String content) {
// 调用邮件服务
}
}
/**
* 具体产品:站内信.
*/
@Component("INNER_MSG")
public class InnerMsgSender implements MessageSender {
@Override
public void send(String target, String content) {
// 写入消息表
}
}
/**
* 工厂类:根据类型获取对应的发送器.
* 利用 Spring 的 Map 注入自动收集所有实现.
*/
@Component
public class MessageSenderFactory {
/** Spring 自动按 Bean 名称注入到 Map 中. */
@Resource
private Map<String, MessageSender> senderMap;
public MessageSender getSender(String type) {
MessageSender sender = senderMap.get(type);
if (sender == null) {
throw new RuntimeException("不支持的消息类型: " + type);
}
return sender;
}
}
/**
* 使用示例.
*/
@Service
public class NoticeService {
@Resource
private MessageSenderFactory factory;
public void sendNotice(String type, String target, String content) {
MessageSender sender = factory.getSender(type);
sender.send(target, content);
}
}
六、代理模式(Proxy Pattern)
概念
为其他对象提供一种代理,以控制对这个对象的访问。代理对象在调用真实对象前后可以加入额外逻辑。
解决的问题
在不修改原有逻辑的前提下增加横切关注点(日志、鉴权、缓存、重试等)。
项目中的体现
- Feign 本身就是动态代理------你写的是接口,Feign 通过 JDK 动态代理生成实现类
- Spring AOP(@Transactional)------事务管理通过代理实现
- FallbackFactory------代理原始 Feign 接口,在调用失败时走降级逻辑
java
// 你写的只是接口
@FeignClient(value = "aaa-order")
public interface aaaOrderFeign {
@PostMapping("/aaa/order/result/receive")
RestControllerResult<Boolean> noticeAa(@RequestBody aaOperationDto param);
}
// Feign 框架在运行时生成代理类,大致逻辑:
通用代码示例
java
/**
* 接口定义.
*/
public interface DataService {
String getData(String key);
}
/**
* 真实实现:从数据库查询.
*/
@Service("dataServiceImpl")
public class DataServiceImpl implements DataService {
@Override
public String getData(String key) {
// 模拟耗时的数据库查询
return database.query(key);
}
}
/**
* 代理实现:增加缓存逻辑.
* 先查缓存,缓存没有再调真实实现.
*/
@Service
@Primary
public class CachedDataServiceProxy implements DataService {
@Resource
@Qualifier("dataServiceImpl")
private DataService realService;
@Resource
private RedisTemplate<String, String> redisTemplate;
@Override
public String getData(String key) {
// 先查缓存
String cached = redisTemplate.opsForValue().get("cache:" + key);
if (cached != null) {
return cached;
}
// 缓存未命中,调用真实实现
String data = realService.getData(key);
// 写入缓存
redisTemplate.opsForValue().set("cache:" + key, data, 5, TimeUnit.MINUTES);
return data;
}
}
七、单例模式(Singleton Pattern)
概念
确保一个类只有一个实例,并提供全局访问点。
项目中的体现
Spring Bean 默认就是单例的。项目中所有 @Service、@Component 注解的类在容器中只有一个实例:
java
@Service
public class OmsServiceImpl implements OmsService { ... } // 单例
@Component
public class EventCenterUtil { ... } // 单例
通用代码示例
java
/**
* 方式一:Spring 管理的单例(推荐).
* 通过 @Component/@Service 注解,Spring 容器保证单例.
*/
@Component
public class GlobalConfig {
private Map<String, String> configMap = new ConcurrentHashMap<>();
public String get(String key) {
return configMap.get(key);
}
public void set(String key, String value) {
configMap.put(key, value);
}
}
/**
* 方式二:双重检查锁(非 Spring 环境).
*/
public class ConnectionPool {
private static volatile ConnectionPool instance;
private ConnectionPool() {
// 初始化连接池
}
public static ConnectionPool getInstance() {
if (instance == null) {
synchronized (ConnectionPool.class) {
if (instance == null) {
instance = new ConnectionPool();
}
}
}
return instance;
}
}