一、前情提要
当一段代码里堆满了if-else判断,每种判断又对应不同的业务逻辑(比如根据不同支付方式处理订单),就会导致代码越写越冗杂。
比如在以下订单模块里,用户购买不同类型的商品(课集、课程、VIP)需要保存不同的购买记录(存到3张不同的表):
// 处理用户购买记录的方法
public void savePaidRecord(UserPaidRecordVo vo) {
// 判断购买的是课集(1001)、课程(1002)还是VIP(1003)
if ("1001".equals(vo.getItemType())) {
// 保存课集购买记录到user_paid_album表
UserPaidAlbum album = new UserPaidAlbum();
album.setUserId(vo.getUserId());
album.setAlbumId(vo.getItemIdList().get(0));
albumMapper.insert(album);
} else if ("1002".equals(vo.getItemType())) {
// 保存网课购买记录到user_paid_track表(可能一次买多个,要循环)
for (Long trackId : vo.getItemIdList()) {
UserPaidTrack track = new UserPaidTrack();
track.setUserId(vo.getUserId());
track.setTrackId(trackId);
trackMapper.insert(track);
}
} else if ("1003".equals(vo.getItemType())) {
// 保存VIP购买记录到user_vip_service表,还要更新用户VIP过期时间
UserVipService vip = new UserVipService();
vip.setUserId(vo.getUserId());
vip.setExpireTime(calculateExpireTime(vo)); // 计算过期时间
vipMapper.insert(vip);
// 更新用户表的VIP状态
UserInfo user = userMapper.selectById(vo.getUserId());
user.setIsVip(1);
user.setVipExpireTime(vip.getExpireTime());
userMapper.updateById(user);
} else {
throw new RuntimeException("未知的商品类型");
}
}
这段代码十分臃肿,所有逻辑堆在一个方法里面,要是后续加了可购买的商品类型,比如SVIP啥的,代码越来越长,而且难以维护。
策略模式的核心思想:
把每种“选择”对应的逻辑单独拆成一个“策略类”,然后用一个“工厂”来管理这些策略,需要的时候直接找工厂要对应策略即可。
对应代码,就是3个步骤:
①抽象策略:定义一个接口,规定每种策略要实现的方法,比如出行接口有“goToWork”方法;
②具体策略:实现抽象策略的类,每种选择对应一个类(比如“开车策略”)
③策略工厂:管理所有具体策略,根据条件返回对应的策略对象(比如你说要开车,工厂就给你开车策略)
二、用策略模式重构「保存购买记录」代码
2.1 定义“抽象策略”接口
创建一个接口,所有商品类型的购买记录保存都要实现这个接口。这个接口里有个savePaidRecord方法,所有“保存购买记录”的策略都要实现该方法。
这个方法就像一个协议,告诉所有具体策略,不管你是课集还是vip,要保存记录都按照这个方法来写:
// 抽象策略接口:所有商品类型的购买记录保存,都要实现这个接口
public interface ItemTypeStrategy {
// 方法:保存购买记录,参数是购买信息VO
void savePaidRecord(UserPaidRecordVo vo);
}
2.2 写“具体策略”类,每种商品类型一个类
把原来if-else的逻辑,拆成3个具体的策略类,分别对应课集、网课、VIP,@Component("1001")里的字符串,要和商品类型编码(1001、1002、1003)一致,后面工厂会用这个编码找对应的策略类:
2.2.1 课集策略类
// 具体策略:课集购买记录保存
@Component("1001") // 注意这个注解里的"1001",对应商品类型编码
public class AlbumStrategy implements ItemTypeStrategy {
@Autowired
private UserPaidAlbumMapper albumMapper;
@Override
public void savePaidRecord(UserPaidRecordVo vo) {
// 这里只写专辑相关的逻辑,和其他类型完全隔离
UserPaidAlbum album = new UserPaidAlbum();
album.setUserId(vo.getUserId());
album.setOrderNo(vo.getOrderNo()); // 订单号,防止重复保存
album.setAlbumId(vo.getItemIdList().get(0));
// 先判断是否已保存,避免重复
LambdaQueryWrapper<UserPaidAlbum> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserPaidAlbum::getOrderNo, vo.getOrderNo());
if (albumMapper.selectCount(wrapper) == 0) {
albumMapper.insert(album);
}
}
}
2.2.2 网课策略类
// 具体策略:网课购买记录保存
@Component("1002") // 商品类型编码1002
public class TrackStrategy implements ItemTypeStrategy {
@Autowired
private UserPaidTrackMapper trackMapper;
@Override
public void savePaidRecord(UserPaidRecordVo vo) {
// 只处理网课的逻辑,一次可能买多个,所以循环
LambdaQueryWrapper<UserPaidTrack> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserPaidTrack::getOrderNo, vo.getOrderNo());
if (trackMapper.selectCount(wrapper) > 0) {
return; // 已保存过,直接返回
}
// 循环保存每个声音的记录
for (Long trackId : vo.getItemIdList()) {
UserPaidTrack track = new UserPaidTrack();
track.setUserId(vo.getUserId());
track.setOrderNo(vo.getOrderNo());
track.setTrackId(trackId);
trackMapper.insert(track);
}
}
}
2.2.3 VIP策略类
// 具体策略:VIP购买记录保存
@Component("1003") // 商品类型编码1003
public class VipStrategy implements ItemTypeStrategy {
@Autowired
private UserVipServiceMapper vipMapper;
@Autowired
private UserInfoMapper userMapper;
@Override
public void savePaidRecord(UserPaidRecordVo vo) {
// 1. 保存VIP购买记录
UserVipService vip = new UserVipService();
vip.setUserId(vo.getUserId());
vip.setOrderNo(vo.getOrderNo());
vip.setStartTime(new Date());
// 计算VIP过期时间(如果是续费,从当前过期时间往后加)
UserInfo user = userMapper.selectById(vo.getUserId());
Date expireTime;
if (user.getIsVip() == 1 && user.getVipExpireTime().after(new Date())) {
// 续费:从当前过期时间加
expireTime = DateUtils.addMonths(user.getVipExpireTime(), 3); // 假设买3个月
} else {
// 新开通:从今天加3个月
expireTime = DateUtils.addMonths(new Date(), 3);
}
vip.setExpireTime(expireTime);
vipMapper.insert(vip);
// 2. 更新用户表的VIP状态
user.setIsVip(1);
user.setVipExpireTime(expireTime);
userMapper.updateById(user);
}
}
三、写“策略工厂”类
工厂的作用就是把具体策略类收集起来,当你需要某个策略时,告诉工厂编码(如1001),工厂就给你返对应的策略对象:
@Component
public class StrategyFactory {
// 核心:Spring会自动把所有实现ItemTypeStrategy接口的类,装进这个Map里
// Map的key:就是@Component注解里的字符串(比如"1001")
// Map的value:对应的策略对象(比如AlbumStrategy实例)
@Autowired
private Map<String, ItemTypeStrategy> strategyMap;
// 方法:根据商品类型编码,获取对应的策略对象
public ItemTypeStrategy getStrategy(String itemType) {
// 从Map里拿对应的策略
ItemTypeStrategy strategy = strategyMap.get(itemType);
if (strategy == null) {
// 如果没有对应的策略,抛异常提示
throw new RuntimeException("没有找到类型为" + itemType + "的策略");
}
return strategy;
}
}
Spring会自动扫描所有实现ItemTypeStrategy的类,把他们放进StrategyMap里。
四、在原来的业务方法里调用策略
原本savePaidRecord本全是if-else,这里就可以改成:
@Service
public class UserInfoServiceImpl {
// 注入策略工厂
@Autowired
private StrategyFactory strategyFactory;
// 处理用户购买记录的方法(现在超级简洁!)
@Transactional(rollbackFor = Exception.class)
public void savePaidRecord(UserPaidRecordVo vo) {
// 1. 从工厂获取对应的策略(比如vo.getItemType()是1001,就拿到AlbumStrategy)
ItemTypeStrategy strategy = strategyFactory.getStrategy(vo.getItemType());
// 2. 调用策略的方法,保存购买记录
strategy.savePaidRecord(vo);
}
}
以后如果要加新的商品类型(比如SVIP,编码1004),只需要新建一个SVIPStrategy类,实现ItemTypeStrategy接口,写SVIP的保存逻辑,再在类上加@Component("1004")注解。
五、知识点总结
1.策略模式优点:告别复杂的if-else、改某个逻辑只动对应的策略类,不会影响其他逻辑。
2.适用场景:多个if-else判断或者switch-case判断,每个判断里逻辑较复杂,且未来可能新增更多逻辑。
3.避坑指南:依赖Spring自动注入Map<String,接口>的特性,不要自己new对象放进Map。
4.流程:定义抽象策略接口 → 写具体策略类 → 用工厂管理策略 → 业务代码调用策略

被折叠的 条评论
为什么被折叠?



