当前位置: 首页 > 科技观察

了解策略模式!但是没有在项目中使用?

时间:2023-03-13 18:27:17 科技观察

前言学习开发的第二年,开始听说要写好代码,必须懂设计模式。于是我就兴致勃勃地读了《Head First 设计模式》。看完之后,对战略模式的形象印象非常深刻。感觉这个模式不错,简单易用,应用广泛。又可以优化一波代码了,于是兴致勃勃的打开了我的IDEA,开全!!!策略模式初体验(错误示范)在讲述我的策略模式初秀之前,我们先来回顾一下策略模式的基本概念。策略模式意图:定义一系列算法,将它们一个一个封装起来,使它们可以互换。主要解决方案:在多个相似算法的情况下,使用if...else复杂且难以维护。何时使用:一个系统有很多很多类,区分它们的是它们的直接行为。简单的说,当做一件事情有多种方式时,可以抽象为一个接口,然后每一种实现都是一个解决方案,调用者可以选择不同的实现。理解之后,我开始重构我们的代码。当时我的第一家公司有这么一段代码,大概就是这个意思(时间久了,凭记忆重写)。有这么一个开奖方式,我们在后台控制中奖率,我们会在不同的时间调整不同的中奖策略。publicclassNumStrategy{enumRandomEnum{/***平均策略*/AVERAGE,/***80%的获胜机会*/RANDOM28;}/***抽奖方式,根据策略不同*@paramrandomEnum*@returntrue:表示中奖false:表示不中奖*/publicbooleanluckDraw(RandomEnumrandomEnum){if(randomEnum.equals(RandomEnum.AVERAGE)){随机random=newRandom();intnum=random.nextInt(100);返回数>=50;}elseif(randomEnum.equals(RandomEnum.RANDOM28)){随机random=newRandom();intnum=random.nextInt(100);返回数字>=20;}返回假;}}我看到了,这不是妥妥的策略模型吗?我们开始做吧。经过一番改造,变成了这样:/***抽奖方法,根据不同的Strategy抽取*@paramrandomEnum*@returnpublicbooleanluckDraw(RandomEnumrandomEnum){if(randomEnum.equals(RandomEnum.AVERAGE)){returnnewAverageStrategy().luckDraw();}elseif(randomEnum.equals(RandomEnum.RANDOM28)){returnnewRandom28Strategy().luckDraw();}返回假;}interfaceLuckDrawStrategy{booleanluckDraw();}classAverageStrategy实现LuckDrawStrategy{@OverridepublicbooleanluckDraw(){Randomrandom=;newRandom()num=random.nextInt(100);返回数>=50;}}类Random28Strategy实现LuckDrawStrategy{@OverridepublicbooleanluckDraw(){Random随机=新随机();intnum=random.nextInt(100);返回数字>=20;}}}修改完我圆满的提交了代码,但是组长审核的时候又改回去了为什么要搞那么多类?我自信地说,我正在使用策略模式来优化代码。他说不用了,我们先改回去吧。我气呼呼地接受了,心想:哎,你连策略模型都不懂?这么多年过去了,我开始明白我当时的做法其实是错误的。代码本来就很简单,里面的逻辑是不会变的,没必要抽象。我的更改似乎设计过度了。把原来的30行代码一对一变成80行,这几年我看到太多次这样写代码了。也就是说,为了使用设计模式而使用设计模式。并且忘了设计模式的初衷是让代码更容易理解、更可靠、更容易维护。甚至看到有人学过策略模式,说项目中所有的if/else都应该安排在策略模式中。花了一年多的时间两次打分,采访的时候也讨论过策略模型。【面试官】Q:你说你用过策略模式,为什么用?[I]:为了分离不同的实现逻辑,优化if/else,让代码更容易理解'使用了很长时间)。我硬着头皮说可以用工厂模式+策略模式来做。【面试官】:那你们工厂模式不也是用if/else判断吗?【我】:。..前额。出色地。..那真的还是需要用if/else来阻止我问。我犹豫的回答确实还是需要if/else判断一次,但是判断移到了工厂模式。下来之后,我又去练习了,想着放到地图里,行不行?publicclassNumStrategy3{enumRandomEnum{/***平均策略*/AVERAGE,/***80%的获胜机会*/RANDOM28;}staticMapmap=newHashMap<>();static{map.put(RandomEnum.RANDOM28,newRandom28Strategy());map.put(RandomEnum.AVERAGE,newAverageStrategy());}/***抽奖方式,根据不同策略抽奖*@paramrandomEnum*@returnpublicbooleanluckDraw(RandomEnumrandomEnum){LuckDrawStrategyluckDrawStrategy=map.get(randomEnum);返回luckDrawStrategy.luckDraw();}interfaceLuckDrawStrategy{booleanluckDraw();新随机();intnum=random.nextInt(100);返回数>=50;}}静态类Random28Strategy实现LuckDrawStrategy{@OverridepublicbooleanluckDraw(){Randomrandom=newRandom();intnum=random.nextInt(100);返回数字>=20;}}}终于解决了if/else的情况,但是这么短的if/else,当里面的逻辑没有太大变化的时候,我个人不推荐使用策略模式,这里只是推荐使用的例子,还有几个多年过去了,原来的菜鸟也成长为老将。当时项目中有这么一段代码:我简化了下面的代码。我们有一个函数来计算页面上的指标。不同的指标对应不同的计算方法。一期页面有4个指标,未来会达到十几个。publicinterfaceTransferService{Stringtransfer();}@ServicepublicclassSearchTransformService{@AutowiredprivateUserTransferServiceuserTransferService;@AutowiredprivateAgeTransferServiceageTransferService;@AutowiredprivateInterestTransferServiceinterestTransferService;/***根据不同的编码进行转换*@paramcode*@returnpublicStringtransform(Stringcode){if(code.equals("user")){returnuserTransferService.transfer();}elseif(code.equals("age")){returnageTransferService.transfer();}elseif(code.equals("interest")){returninterestTransferService.transfer();}返回””;}}可以看到在这样的业务场景下,if/else的写法会很长,后面有十几个的情况下很难维护。另外代码中使用了magicnumber,也是一种不好的写法。我优化如下:首先将代码定义为枚举枚举CodeEnum{USER("user"),AGE("age"),INTEREST("interest"),;私有字符串代码;publicStringgetCode(){返回代码;}CodeEnum(Stringcode){this.code=code;}privatestaticfinalMapmap=Arrays.stream(CodeEnum.values()).collect(Collectors.toMap(CodeEnum::getCode,Function.identity()));publicCodeEnumof(Stringcode){returnmap.get(code);}}在原来的接口上增加一个transCode方法,每个实现都需要声明对应的代码。公共接口TransferService{字符串传输();CodeEnumtransCode();}@ServicepublicclassAgeTransferServiceimplementsTransferService{@OverridepublicStringtransfer(){returnnull;}@OverridepublicCodeEnumtransCode(){returnCodeEnum.AGE;}}使用map存储编码对应的实现类的关联关系,获取对应的转换器实现类@ServicepublicclassSearchTransformServiceimplementsInitializingBean{@AutowiredprivateListtransferServiceList;privateMaptransferServiceMap;@Override//项目启动时将实现类放入mappublicvoidafterPropertiesSet()throwsException{transferServiceMap=transferServiceList.stream().collect(Collectors.toMap(TransferService::transCode,Function.identity()));}/***根据不同的编码进行转换*@paramcode*@returnpublicStringtransform(Stringcode){TransferServicetransferService=transferServiceMap.get(CodeEnum.of(code));Assert.notNull(transferService,"找不到对应的转换器");返回transferService.transfer();}}重构后是不是很简洁?如果后面添加新的代码转换器,只需要先在枚举中定义,然后添加实现类的实现方法即可。无需关心如何调用,只关心具体的实现逻辑,减少维护成本。它是策略模式的真正应用。别闹了哈哈哈