草履虫都能看懂的“策略模式”!从0到1解决项目中的if-else地狱!

一、前情提要

当一段代码里堆满了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.流程:定义抽象策略接口 → 写具体策略类 → 用工厂管理策略 → 业务代码调用策略

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值