一次策略设计模式的实际应用

扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,阅读更多Spring源码分析文章。

微信公众号

平时工作当中,总是听到要学习设计模式,设计模式的优点很多,我们要掌握好设计模式,包括面试当中也经常会被问到设计模式这些东西。但是平时只顾着学了,很少在工作中实际用到,不是说工作中用不到设计模式,而是平时积累得太少了,碰到了相应的场景,我们自己压根就没有往这方面思考。这不,笔者在写了成百上千次if…else以后,终于想起了在项目中使用设计模式了。

  • 笔者所在公司是做激励性广告媒体投放的,说得通俗点,就是给用户钱,让用户点击我们APP上的广告。既然是激励性的,所以就涉及到给用户加奖励,作为一名后端开发,就需要提供加奖励的接口。由于广告场景很多,每一个地方给用户奖励的条件又是不一样的,所以在代码实现上就针对每一个场景做了不同处理。笔者作为一名菜鸟,前期的代码就是通过if…else来判断的,直到后来,可能是if…else写得太多了,突然间开窍了,对原有代码用设计模式进行了一下优化。
  • 本文选取了三个加奖励的广告场景:1. 当用户试玩大转盘时,会给用户奖励;2.当用户试玩APP中的游戏时,会给用户奖励;3. 当用户签到时,又会给用户奖励。这三个奖励的场景,领取奖励的条件都不一样,为了防刷,所以在后端需要做出各种不同的判断。

1. if…else时代

  • 先来看看优化之前代码。代码中的DialService、GameService、SignService分别表示用来处理大转盘加奖励、游戏加奖励、签到加奖励的service类。
public class AdServiceImpl implements AdService {

    @Autowired
    private DialService dialService;

    @Autowired
    private GameService gameService; 

    @Autowired
    private SignService signService;    

    @Override
    public Response reward(Reward reward) {
        int type = reward.getType();
        if (type == 1){
            return dialService.reward();
        } else if(type == 2){
            return gameService.reward();
        } else if(type == 3){
            return signService.reward();
        }
        // 如果有其他的广告场景,就得在下面一行加一个else if的判断,然后再利用Autowired注入一个对应的service来处理。
        return null;
    }
}
  • 从上面的代码中可以看到,每增加一种奖励的场景,我都需要加一个else if的判断,由于笔者所在的公司,加奖励的场景实在是太多了,至少有20个以上,那么这儿加20个以上的else if显然就不太合理了(在实际代码中,虽然不是这么干的,但写得比这种做法还low)。
  • 于是就开始思考应该如何重构,看到这么多的if…else笔者首先想到的就是策略设计模式,毕竟平时在很多公众号中都看到用策略设计模式来替换掉if…else。而且在笔者碰到场景中,对于每一种处理奖励的service,它们都只有一个方法,例如DialService、GameService、SignService都只有一个reward()方法,就是用来处理加奖励的逻辑的,唯一不同的就是它们各自的处理逻辑不一样。这正好符合策略设计模式的思想。

2. 策略设计模式的思想与特点

  • 首先了解下策略设计模式的思想和特点是什么。
  • 思想:其思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而使它们可以相互替换。策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能。
  • 从思想中我们能看到策略设计模式的特点就是:面向接口编程。
  • 例如本文中加奖励场景,无论是大转盘加奖励,还是游戏加奖励,或者是签到加奖励,都是加奖励,我们可以给他们定义一个接口BaseRewardService,接口中有一个方法reward(),然后让DialService、GameService、SignService分别实现BaseRewardService,重写reward()方法,让它们分别针对不同的加奖励场景,实现自己不同的逻辑,最后在调用时,使用BaseRewardService.reward(),这样当注入的BaseRewardService是哪一种实现类时,就会调用到不同的reward()方法。

3. 重构之后

  • 重构之后的代码
public class public class AdServiceImpl implements AdService  {

    @Autowired
    private Map<String,BaseRewardService> rewardServiceMap;

    @Override
    public Response reward(Reward reward) {
        RewardType rewardType = RewardType.find(reward.getRewardType());
        if(rewardType == null){
            return null;
        }
        BaseRewardService rewardStrategy = rewardServiceMap.get(rewardType.strategyName());
        return rewardStrategy.reward(rewardDTO);
    }
}
  • 在重构的代码中,我们通过@Autowired注解,为属性注入了一个Map类型的值,这个map中,key是DialService、GameService、SignService这几个service在容器中的beanName,值是容器中所有类型为BaseRewardService的Bean,即DialService、GameService、SignService这些bean。这是Spring的IOC一个比较厉害的功能。至于Spring为什么能注入Map,可以看看笔者的上一篇文章《@Autowired注解的实现原理》
  • RewardType是一个枚举类型,在枚举类型中定义了一个strategyName属性,该属性表示的是加奖励的service在Spring容器中的beanName,该枚举还提供了一个find()方法,该方法是根据传入的code值,找到对应的枚举类型,定义如下:
public enum RewardType {
    DIAL(1, "dialService", "大转盘奖励"),
    GAME(2, "gameService", "游戏奖励"),
    SIGN(3, "signService", "签到奖励");

    public static RewardType find(int code) {
        for (RewardType type : RewardType.values()) {
            if (type.code == code) {
                return type;
            }
        }
        return null;
    }

    RewardType(int code, String strategyName, String desc) {
        this.code = code;
        this.strategyName = strategyName;
        this.desc = desc;
    }

    private int code;

    private String strategyName;

    private String desc;

    public int code() {
        return code;
    }

    public String strategyName() {
        return strategyName;
    }

    public String desc() {
        return desc;
    }
}
  • 当需要为哪个场景添加奖励时,我们可以根据type值找到对应的枚举,然后根据对应的枚举,就能知道对应service的beanName,这样我们通过rewardServiceMap.get(rewardType.strategyName())就能知道应该具体使用哪一个BaseRewardService的实现类来进行处理了。
  • 这样我们就成功干掉了if…else这样的代码了。而且以后,出现新的加奖励广告场景,我们根本不需要改动AdServiceImpl中的代码,只需要新增一个枚举值,然后再创建一个BaseRewardService接口的实现类即可。这样不仅代码看起来更优雅,而且更便于后期的维护。缺点是:产生的类文件会有点多。

4. 更深层次的优化

  • 本以为啊,笔者这一次优化已经让代码优化得挺好的了,心里挺高兴的,终于他丫的把设计模式用上了。不过后面看了组里另外一位同事的优化,发现他做的更好。
  • 因为对于DialService、GameService、SignService中,reward()方法的代码实现逻辑都不一样,但是它们都有相同的特点:为了接口的并发请求,所以会用到分布式锁;校验请求参数是否合法;用户是否可以领取;以及记录用户已领取奖励的状态;然后调用加金币的微服务接口。所以呢,这位同事又将这些操作进行了一次抽象,采用了模板设计模式。把加锁、校验、更新缓存、调微服务接口加金币这四步又分别提取成抽象方法了,这样再让DialService、GameService、SignService去具体实现这几个抽象方法,最后代码看起来更加简洁与优雅。(这一步优化后的代码,由于是另外一位同事的优化成果,所以就不贴出代码了)。
  • 这位同事的优化,让笔者这位菜鸟又学习了不少。这次优化,这让笔者再次体会到,平时得多向他人学习,平时多总结,多思考,这样才能有进步,学有所用。

微信公众号

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值