• 中文
    • English
  • 注册
  • 查看作者
  • 从ifelse到策略模式,谈谈我对设计模式的理解

    前言一提到设计模式大家都会觉得很厉害,但是要用好设计模式确实不容易。甚至有很多人都不知道该在什么场景下使用设计模式。不过最近我在做一个项目时,通过大佬的一番指点,将策略模式运用到了项目之中。后来我仔细思考了一下,好像有点悟了,其实以前做过的很多项目中都可以运用到策略模式,而且使用策略模式后,代码的耦合度会降低扩展性也会增强。

    接下来我会结合一个具体的案例,从一开始的不用设计模式,一步步地优化代码,来聊一聊该如何使用策略模式。

    从ifelse到策略模式的进化假如现在有这样一个活动。随机给用户抽取十道题目,如果用户答对其中6道题,就可以获得一份礼品。

    这个功能的实现包括抽取题目、判断用户回答正确的题目数、发放礼品等多个环节。现在只针对判断用户回答正确的题目数这一个环节进行讲解。

    Step1:一撸到底现在的需求还是比较简单的,就是循环比对用户的答案与数据库中的答案。直接开撸即可,不需要任何花哨的技巧也可以轻松地完成。

    @Servicepublic class ActivityServiceImpl implements ActivityService {

    }复制代码代码很简单,也很好地实现了功能。如果需求没有进行变更,当然没有问题,但要是需求改变了,代码也要随之更改。

    Step2:if…else…当程序员开发完成后,运维以及产品经理在一起研究讨论发现。现在的活动规则过于简单,少了一些趣味性。为了适当地增加一些趣味性以及挑战性,将整个答题活动分为了三个关卡,关卡由易到难分别为简单、中等、困难,三个关卡都通过才能获得礼品。题目也设为了三个等级:简单、中等、困难。

    简单模式:10道简单题。答对其中6道即算过关。中等模式:5道简单题,3道中等题,2道困难题。答对3道简单题,2道中等题,1道困难题即算过关。困难模式:6道中等题,4道困难题。答对4道中等题,2道困难题即算过关。@Servicepublic class ActivityServiceImpl implements ActivityService {

    }复制代码这个时候,需求的复杂度已经提升了一个等级。虽然从实现上来说也没有什么难度,不过是答案的判断而已。但是当代码写完后会发现,里面有一大坨的if…else…。如果后续需求再次发生变化或者有bug。去定位需要修改的位置也要耗费一定的时间,代码的可维护性就会降低。如果后续再推出第4关,第5关,那么将会有更多的if…else…,所以这种方式也不具备良好的扩展性。最关键的是,这种方式写出来的代码将会很难看,对于一个追求代码整齐、清晰的人来说,简直不能够容忍。

    Step3:使用策略模式优化代码我们先来看一下策略模式的定义:

    指定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

    从定义上来看,好像策略模式用起来很不错的样子,那我们就来具体实现一下:

    首先在入参的 AnswersSubmitReq 中添加一个字段,用于标识将要采取哪一个策略

    @Datapublic class AnswersSubmitReq {

    }复制代码然后再去新建一个策略接口,所有的策略实现都去实现这个接口。

    public interface ICommitAnswer {

    }复制代码这个就是策略的接口,策略的实现类都去实现这个接口然后实现其中的execute方法。

    public class EasyCommitAnswer implements ICommitAnswer {

    }

    public class MediumCommitAnswer implements ICommitAnswer {

    }

    public class HardCommitAnswer implements ICommitAnswer {

    }复制代码现在我们只需要根据不同的场景去调用不同的策略就可以了:

    @Servicepublic class ActivityServiceImpl implements ActivityService {

    }复制代码策略模式到这里就差不多完成了。具体的策略都由不同的策略实现类决定,与调用方无关,Service层的代码看起来也整齐多了。如果后续某个策略要进行修改,那么去修改对应的策略就好,调用方不需要修改。如果要增加新的策略,那么Service层也只需要进行简单的调整就可以。可维护性与扩展性都大大地得到了提升。

    Step4:策略模式再优化看样子上面的代码好像没有什么问题了,但是Service层在调用策略的时候,不还是要通过if…else…来进行判断吗。只不过是从代码流程的切换变为了对策略调用的判断。

    其实这也是可以解决的。首先我们要知道一点,就是外部肯定是知道它自己是要调用哪个策略的,所以我们只需要给每个策略编一个号,外部调用时传个编号过来(上一节的answerMode字段)。我们通过一个Map将所有的策略都装起来,编号就作为Map的key,那通过key不就可以取到对应的策略类了嘛。

    public class CommitAnswerFactory {private static final Map answerStrategies = new HashMap<>();

    }复制代码在策略的工厂类中,通过一个Map将策略的对象放入其中,然后提供一个getAnswerStrategy方法,只要将策略的编号传入,就可以从Map中取出对应的策略实现类了。这样Service层在调用时就不需要使用if…else…进行判断了

    @Servicepublic class ActivityServiceImpl implements ActivityService {

    }复制代码Step5:进一步抽取公共代码,简化代码不知道大家有没有发现,三个策略中好像都有一段很相似的代码,就是对于正确答案的判断。仔细分析三个策略就可以发现,其实三个策略中不同的地方仅仅是在于对结果的判断,而统计不同难度答对题目的数量操作都是相同的,都是循环比对用户答案与数据库中的答案是否一致,然后进行计数。

    既然有公共的地方就可以提取出来,那么提取到哪里比较合适呢?既然三个策略都实现了ICommitAnswer接口,那么不如就将公共代码放入ICommitAnswer接口中去。

    public interface ICommitAnswer {

    }复制代码现在在接口中添加了computerRightCount方法,并为其添加了默认实现,这个方法就是计算各个难度的题目分别答对了多少题。然后将结果放入一个Map集合中。

    @Servicepublic class ActivityServiceImpl implements ActivityService {

    }复制代码这样在具体的策略中只需要对正确答案的数量进行判断即可。

    public class EasyCommitAnswer implements ICommitAnswer {

    }

    public class MediumCommitAnswer implements ICommitAnswer {

    }

    public class HardCommitAnswer implements ICommitAnswer {

    }复制代码上述的案例中,并不只有这一个地方可以使用策略模式。抽取题目不也分为几种情况吗,那么这不也可以使用策略模式进行包装吗

    策略模式实现业务之间的解耦其实策略模式不仅可以实现上述这种不同业务流程之间的切换,也可以实现不同业务之间的解耦。比如我最近在做的一个项目,我需要对表中的某个字段进行更新,但是更新却分为了几种情况,这几种情况分别散落在不同的业务中。这其实是一件非常恶心的事,因为一个业务中竟然掺杂了对其它业务的处理,如果日后是别人接手了我的代码,那么他看到某个业务中出现了这样一段代码肯定会一脸懵逼。这个地方为什么要对这个字段进行更新?到底还有哪些地方对这个字段进行了操作?所以可维护性就很差。不说别人,可能过段时间过后,我自己都忘了为什么要这么写了。

    其实我一开始并没有意识到这个问题,但是通过大佬的一番指点,我采用了策略模式去实现,将对该字段的操作封装成几个策略,然后在不同的业务场景下调用不同的策略。因为一个策略我只调用了一次,所以通过查看这几个策略分别在哪些地方被调用了,我就可以知道有哪些地方对这个字段进行了操作。优点就是代码更加清晰了,维护起来也方便了,同时也避免了多个业务之间的耦合。

    总结其实使用一个设计模式并不一定要完全照搬,因为使用设计模式的目的还是为了代码的整洁、可维护性与可扩展性。所以在使用的时候可以按照自己的使用场景做适当的调整。最重要的还是要理解不同的设计模式到底解决了什么问题,适用于什么场景。当你感觉一段代码写完后看起来感觉比较恶心的时候,就应该思考,是不是可以使用某种设计模式去优化代码。

    以上就是我这段时间在项目中使用过策略模式后的一些思考与总结,有问题欢迎在评论区留言讨论!

  • 0
  • 0
  • 0
  • 9
  • 请登录之后再进行评论

    登录
  • 任务
  • 实时动态
  • 发布
  • 单栏布局 侧栏位置: