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

复杂的业务=否则?刚来的高手居然用攻略+factory将他们彻底干掉!

时间:2023-03-19 19:02:35 科技观察

为了业务的发展,业务逻辑的复杂化是不可避免的。随着业务的发展,需求只会越来越复杂。为了考虑各种情况,难免会有很多if-else。一旦代码中出现过多的if-else,就会极大地影响其可读性和可维护性。首先是可读性,不用说过多的if-else代码和嵌套会让阅读代码的人难以理解其中的含义。特别是那些没有注释的代码。其次是可维护性,因为if-else太多了,当你想增加一个新的分支时,会很难增加,极易影响其他分支。笔者曾经看过一个支付核心应用,它支持很多商家的在线支付功能,但是每个商家都有很多的定制需求,所以很多核心代码都有很多if-else。当每一个新的业务需要定制的时候,把自己的if放在整个方法的最前面,保证你的逻辑能够正常执行。这种做法的后果可想而知。事实上,有办法消除if-else。典型和广泛使用的是策略模式和工厂模式的使用。准确的说,就是利用这两种设计模式的思想,彻底杜绝代码中的if-else。.本文结合这两种设计模式,介绍了如何剔除if-else,同时也介绍了如何与Spring框架相结合,以便读者看完本文后可以立即应用到自己的项目中。本文涉及一些代码,但作者尽量使用流行的示例和伪代码来使内容不那么枯燥。恶心的if-else假设我们要做一个外卖平台,我们有如下需求:1、外卖平台某店铺设置了多种会员优惠促销,包括20%的折扣超级会员和普通会员10%的折扣普通用户有三种折扣。2、希望用户在支付的时候,根据用户的会员级别,能够知道用户满足了哪种折扣策略,然后进行打折,计算出应付金额。3、随着业务的发展,新的需求要求专享会员必须在店铺订单金额大于30元时才能享受优惠。4、接下来还有一个异常需求。如果用户的超级会员已过期,且过期时间在一周以内,则用户的单笔订单将根据超级会员进行折扣,并在收银处进行强烈提醒,引导用户开通再次成为会员,优惠只进行一次。那么,我们可以看到如下伪代码:publicBigDecimalcalPrice(BigDecimalorderPrice,StringbuyerType){if(用户是尊享会员){if(订单金额大于30元){返还30%的价格;}}if(该用户为超级会员){return20%offPrice;}if(该用户为普通会员){if(该用户超级会员刚过期,临时折扣尚未使用){临时折扣使用次数更新();return20%offprice;}return10%offprice;}returnoriginalprice;}以上,就是针对这个需求的一段价格计算逻辑。使用伪代码是如此复杂。如果真的把代码写出来,复杂度可想而知。这样的代码中,if-else很多,if-else的嵌套也很多,可读性和可维护性都很低。那么,如何改进呢?策略模式接下来,我们尝试引入策略模式来提高代码的可维护性和可读性。首先定义一个接口:/***@authormhcoding*/publicinterfaceUserPayService{/***计算应付价格*/publicBigDecimalquote(BigDecimalorderPrice);}然后定义几个策略类:/***@authormhcoding*/publicclassParticularlyVipPayServiceimplementsUserPayService{@OverridepublicBigDecimalquote(BigDecimalorderPrice){if(消费金额大于30元){return30%offprice;}}}publicclassSuperVipPayServiceimplementsUserPayService{@OverridepublicBigDecimalquote(BigDecimalorderPrice){return20%offprice;}}publicclassVipPayServiceimplementsUserPayService{@OverridepublicBigDecimalquoteUserPayService{@OverridepublicBigDecimalquote)superexpireBigDecimalquote(member(BigDecimalif且临时优惠尚未使用){临时优惠使用号update();return20%offprice;}return10%offprice;}}引入策略后,我们可以这样计算价格:/***@authormhcoding*/publicclassTest{publicstaticvoidmain(String[]args){UserPayServicestrategy=newVipPayService();BigDecimalquote=strategy.quote(300);System.out.println("普通会员产品最终价格为:"+quote.doubleValue());strategy=newSuperVipPayService();quote=strategy.quote(300);System.out.println("超级会员商品最终价格为:"+quote.doubleValue());}}以上为示例,可以在代码中新建不同会员的策略类,然后执行相应的计算价格的方法。读者可以在文章《如何给女朋友解释什么是策略模式?》中了解本例以及策略模式的相关知识。但是如果真正用在代码中,比如在web项目中,上面的demo根本就不能直接使用。首先,在web项目中,我们上面创建的策略类都是由Spring管理的,我们不会自己新建实例。其次,在web项目中,如果真的要计算价格,还需要提前知道用户的会员级别,比如从数据库中查询会员级别,然后根据级别获取不同的策略类执行价格计算方法。那么,web项目中真正的价格计算,伪代码应该是这样的:){//伪代码:从Spring中获取超级会员的策略对象(SuperVipPayService.class);returnstrategy.quote(orderPrice);}if(vipType==普通会员){UserPayServicestrategy=Spring.getBean(VipPayService.class);returnstrategy.quote(orderPrice);}返回原价;}通过上面代码,我们发现,代码的可维护性和可读性似乎更好了,但是if-else似乎并没有降低。其实在之前的文章《如何给女朋友解释什么是策略模式?》中,我们介绍了策略模式的很多优点。但是,策略模式的使用还有一个比较大的缺点:客户端必须知道所有的策略类,并决定使用哪个策略类。这意味着客户端必须了解这些算法之间的区别,以便在正确的时间选择合适的算法类。也就是说,虽然在计算价格的时候没有if-else,但是在选择具体策略的时候难免有些if-else。另外,在上面的伪代码中,我们实现伪代码是从Spring获取会员的policy对象,那么代码是如何获取对应的Bean的呢?下面我们就看看如何借助Spring和工厂模式来解决上述问题。工厂模式为了方便我们从Spring获取UserPayService的各种策略类,我们创建一个工厂类:/***@authormhcoding*/publicclassUserPayServiceStrategyFactory{privatestaticMapservices=newConcurrentHashMap();publicstaticUserPayServicegetByUserType(Stringtype){returnservices.get(type);}publicstaticvoidregister(StringuserType,UserPayServiceuserPayService){Assert.notNull(userType,"userTypecan'tbenull");services.put(userType,userPayService);}}这个UserPayServiceStrategyFactory定义一个Map,用于保存所有策略类的实例,并提供了一个getByUserType方法,可以根据类型直接获取对应的类实例。还有一个register方法,后面会讲到。有了这个工厂类,可以大大优化价格计算代码:returnstrategy.quote(orderPrice);}在上面的代码中,不再需要if-else。获取到用户的vip类型后,可以直接通过工厂的getByUserType方法调用。通过strategy+factory,我们的代码得到了很大程度的优化,大大提高了可读性和可维护性。不过上面还留有一个问题,就是UserPayServiceStrategyFactory中用来存放所有策略类实例的Map是如何初始化的呢?每个策略的实例对象是怎么塞进去的?SpringBean的注册还记得我们前面说的register方法是在定义的UserPayServiceStrategyFactory中提供的吗?用于注册策略服务。接下来我们就想办法调用register方法,通过IOC注册Spring创建的Bean。对于这个需求,可以借用Spring提供的InitializingBean接口。该接口提供了Bean在属性初始化后的处理方法。它只包括afterPropertiesSet方法。所有继承这个接口的类都会在bean的属性初始化后执行这个方法。那么,我们可以稍微修改一下前面的策略类:/***@authormhcoding*/@ServicepublicclassParticularlyVipPayServiceimplementsUserPayService,InitializingBean{@OverridepublicBigDecimalquote(BigDecimalorderPrice){if(消费金额大于30元){返还30折价;}}@OverridepublicvoidafterPropertiesSet()throwsException{UserPayServiceStrategyFactory.register("ParticularlyVip",this);}}@ServicepublicclassSuperVipPayServiceimplementsUserPayService,InitializingBean{@OverridepublicBigDecimalquote(BigDecimalorderPrice){return8折价;}@OverridepublicvoidafterPropertiesSet()throwsException{UserPayServiceStrategyFactoryV.register("Super);}}@ServicepublicclassVipPayServiceimplementsUserPayService,InitializingBean{@OverridepublicBigDecimalquote(BigDecimalorderPrice){if(用户的超级会员刚过期,还没有使用临时折扣){临时折扣使用次数update();返还20折价;}返还10折价格;}@OverridepublicvoidafterPropertiesSet()throwsException{UserPayServiceStrategyFactory.register("Vip",this);}}只要求每个策略服务实现类实现InitializingBean接口,并实现其afterPpropertiesSet方法,在该方法中调用UserPayServiceStrategyFactory.register。这样,在Spring初始化时,当创建VipPayService、SuperVipPayService和SpecializedVipPayService时,Bean的属性初始化完成后,就会在UserPayServiceStrategyFactory中注册Bean。其实上面的代码中还有一些重复的代码,可以引入模板方法模式进一步简化,这里就不展开了。另外调用UserPayServiceStrategyFactory.register时,第一个参数需要传一个字符串,这里其实可以优化一下。比如使用枚举,或者在每个策略类中自定义一个getUserType方法,分别实现。总结这篇文章,我们通过策略模式、工厂模式和Spring的InitializingBean,提高了代码的可读性和可维护性,彻底去掉了一堆if-else。您可以立即尝试本文中的方法。这种做法在我们日常开发中经常会用到,而且还有很多衍生用法,也很有用。以后有机会再介绍。其实,如果读者对策略模式和工厂模式有所了解,本文所用的并不是严格意义上的策略模式和工厂模式。首先,这里没有策略模式中重要的Context角色。没有Context,就没有组合方法,取而代之的是一个工厂。另外,这里的UserPayServiceStrategyFactory其实只是维护了一个Map,并提供了register和get方法,而工厂模式实际上是帮助创建对象,这里并没有用到。因此,读者不必担心是否真的使用了策略模式和工厂模式。而且,这里再多说一句,所谓的GOF23种设计模式,无论你看哪本书或者博客,都是简单的代码示例,但是我们日常开发很多都是基于Spring等框架,没办法直接拿来用.所以对于设计模式的学习,重要的是学习它的思想,而不是代码实现!!!如果读者有兴趣,可以稍后发布更多将设计模式与Spring和其他框架相结合的最佳实践。希望通过这样的文章,让读者能够真正在代码中使用设计模式。