您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

刚来的大神彻底干掉了代码中的if else...

时间:2019-11-26 14:13:48  来源:  作者:

对于业务开发来说,业务逻辑的复杂是必然的。随着业务发展,需求只会越来越复杂,为了考虑到各种各样的情况,代码中不可避免的会出现很多 if-else。

刚来的大神彻底干掉了代码中的if else...

 


图片来自 Pexels

一旦代码中 if-else 过多,就会大大的影响其可读性和可维护性。

刚来的大神彻底干掉了代码中的if else...

 

首先可读性,不言而喻,过多的 if-else 代码和嵌套,会使阅读代码的人很难理解到底是什么意思。尤其是那些没有注释的代码。

其次是可维护性,因为 if-else 特别多,想要新加一个分支的时候,就会很难添加,极其容易影响到其他的分支。

笔者曾经看到过一个支付的核心应用,这个应用支持了很多业务的线上支付功能,但是每个业务都有很多定制的需求,所以很多核心的代码中都有一大坨 if-else。

每个新业务需要定制的时候,都把自己的 if 放到整个方法的最前面,以保证自己的逻辑可以正常执行。这种做法,后果可想而知。

其实,if-else 是有办法可以消除掉的,其中比较典型的并且使用广泛的就是借助策略模式和工厂模式,准确的说是利用这两个设计模式的思想,彻底消灭代码中的 if-else。

本文就结合这两种设计模式,介绍如何消除 if-else,并且,还会介绍如何和 Spring 框架结合,这样读者看完本文之后就可以立即应用到自己的项目中。

本文涉及到一些代码,但是作者尽量用通俗的例子和伪代码等形式使内容不那么枯燥。

恶心的 if-else

假设我们要做一个外卖平台,有这样的需求:

  1. 外卖平台上的某家店铺为了促销,设置了多种会员优惠,其中包含超级会员折扣 8 折、普通会员折扣 9 折和普通用户没有折扣三种。
  2. 希望用户在付款的时候,根据用户的会员等级,就可以知道用户符合哪种折扣策略,进而进行打折,计算出应付金额。
  3. 随着业务发展,新的需求要求专属会员要在店铺下单金额大于 30 元的时候才可以享受优惠。
  4. 接着,又有一个变态的需求,如果用户的超级会员已经到期了,并且到期时间在一周内,那么就对用户的单笔订单按照超级会员进行折扣,并在收银台进行强提醒,引导用户再次开通会员,而且折扣只进行一次。

那么,我们可以看到以下伪代码:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) { if (用户是专属会员) { if (订单金额大于30元) { returen 7折价格; } } if (用户是超级会员) { return 8折价格; } if (用户是普通会员) { if(该用户超级会员刚过期并且尚未使用过临时折扣){ 临时折扣使用次数更新(); returen 8折价格; } return 9折价格; } return 原价; } 

以上,就是对于这个需求的一段价格计算逻辑,使用伪代码都这么复杂,如果是真的写代码,那复杂度可想而知。

这样的代码中,有很多 if-else,并且还有很多的 if-else 的嵌套,无论是可读性还是可维护性都非常低。那么,如何改善呢?

策略模式

接下来,我们尝试引入策略模式来提升代码的可维护性和可读性。

首先,定义一个接口:

/** * @author mhcoding */ public interface UserPayService { /** * 计算应付价格 */ public BigDecimal quote(BigDecimal orderPrice); } 

接着定义几个策略类:

/** * @author mhcoding */ public class ParticularlyVipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) { if (消费金额大于30元) { return 7折价格; } } } public class SuperVipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) { return 8折价格; } } public class VipPayService implements UserPayService { @Override public BigDecimal quote(BigDecimal orderPrice) { if(该用户超级会员刚过期并且尚未使用过临时折扣){ 临时折扣使用次数更新(); returen 8折价格; } return 9折价格; } } 

引入了策略之后,我们可以按照如下方式进行价格计算:

/** * @author mhcoding */ public class Test { public static void main(String[] args) { UserPayService strategy = new VipPayService(); BigDecimal quote = strategy.quote(300); System.out.println("普通会员商品的最终价格为:" + quote.doubleValue()); strategy = new SuperVipPayService(); quote = strategy.quote(300); System.out.println("超级会员商品的最终价格为:" + quote.doubleValue()); } } 

以上,就是一个例子,可以在代码中 New 出不同的会员的策略类,然后执行对应的计算价格的方法。

但是,真正在代码中使用,比如在一个 Web 项目中使用,上面这个 Demo 根本没办法直接用。

首先,在 Web 项目中,上面我们创建出来的这些策略类都是被 Spring 托管的,我们不会自己去 New 一个实例出来。

其次,在 Web 项目中,如果真要计算价格,也是要事先知道用户的会员等级,比如从数据库中查出会员等级,然后根据等级获取不同的策略类执行计算价格方法。

那么,Web 项目中真正的计算价格的话,伪代码应该是这样的:

/** * @author mhcoding */ public BigDecimal calPrice(BigDecimal orderPrice,User user) { String vipType = user.getVipType(); if (vipType == 专属会员) { //伪代码:从Spring中获取超级会员的策略对象 UserPayService strategy = Spring.getBean(ParticularlyVipPayService.class); return strategy.quote(orderPrice); } if (vipType == 超级会员) { UserPayService strategy = Spring.getBean(SuperVipPayService.class); return strategy.quote(orderPrice); } if (vipType == 普通会员) { UserPayService strategy = Spring.getBean(VipPayService.class); return strategy.quote(orderPrice); } return 原价; } 

通过以上代码,我们发现,代码可维护性和可读性好像是好了一些,但是好像并没有减少 if-else 啊。

但是,策略模式的使用上,还是有一个比较大的缺点的:客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。

也就是说,虽然在计算价格的时候没有 if-else 了,但是选择具体的策略的时候还是不可避免的还是要有一些 if-else。

另外,上面的伪代码中,从 Spring 中获取会员的策略对象我们是伪代码实现的,那么代码到底该如何获取对应的 Bean 呢?

接下来我们看如何借助 Spring 和工厂模式,解决上面这些问题。

工厂模式

为了方便我们从 Spring 中获取 UserPayService 的各个策略类,我们创建一个工厂类:

/** * @author mhcoding */ public class UserPayServiceStrategyFactory { private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>(); public static UserPayService getByUserType(String type){ return services.get(type); } public static void register(String userType,UserPayService userPayService){ Assert.notNull(userType,"userType can't be null"); services.put(userType,userPayService); } } 

这个 UserPayServiceStrategyFactory 中定义了一个 Map,用来保存所有的策略类的实例,并提供一个 getByUserType 方法,可以根据类型直接获取对应的类的实例。还有一个 Register 方法,这个后面再讲。

有了这个工厂类之后,计算价格的代码即可得到大大的优化:

/** * @author mhcoding */ public BigDecimal calPrice(BigDecimal orderPrice,User user) { String vipType = user.getVipType(); UserPayService strategy = UserPayServiceStrategyFactory.getByUserType(vipType); return strategy.quote(orderPrice); } 

以上代码中,不再需要 if-else 了,拿到用户的 vip 类型之后,直接通过工厂的 getByUserType 方法直接调用就可以了。

通过策略+工厂,我们的代码很大程度的优化了,大大提升了可读性和可维护性。

但是,上面还遗留了一个问题,那就是 UserPayServiceStrategyFactory 中用来保存所有的策略类的实例的 Map 是如何被初始化的?各个策略的实例对象如何塞进去的呢?

Spring Bean 的注册

还记得我们前面定义的 UserPayServiceStrategyFactory 中提供了的 Register 方法吗?他就是用来注册策略服务的。

接下来,我们就想办法调用 Register 方法,把 Spring 通过 IOC 创建出来的 Bean 注册进去就行了。

这种需求,可以借用 Spring 中提供的 InitializingBean 接口,这个接口为 Bean 提供了属性初始化后的处理方法。

它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在 Bean 的属性初始化后都会执行该方法。

那么,我们将前面的各个策略类稍作改造即可:

/** * @author mhcoding */ @Service public class ParticularlyVipPayService implements UserPayService,InitializingBean { @Override public BigDecimal quote(BigDecimal orderPrice) { if (消费金额大于30元) { return 7折价格; } } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("ParticularlyVip",this); } } @Service public class SuperVipPayService implements UserPayService ,InitializingBean{ @Override public BigDecimal quote(BigDecimal orderPrice) { return 8折价格; } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("SuperVip",this); } } @Service public class VipPayService implements UserPayService,InitializingBean { @Override public BigDecimal quote(BigDecimal orderPrice) { if(该用户超级会员刚过期并且尚未使用过临时折扣){ 临时折扣使用次数更新(); returen 8折价格; } return 9折价格; } @Override public void afterPropertiesSet() throws Exception { UserPayServiceStrategyFactory.register("Vip",this); } } 

只需要每一个策略服务的实现类都实现 InitializingBean 接口,并实现其 afterPropertiesSet 方法,在这个方法中调用 UserPayServiceStrategyFactory.register 即可。

这样,在 Spring 初始化的时候,当创建 VipPayService、SuperVipPayService 和 ParticularlyVipPayService 的时候,会在 Bean 的属性初始化之后,把这个 Bean 注册到 UserPayServiceStrategyFactory 中。

以上代码,其实还是有一些重复代码的,这里面还可以引入模板方法模式进一步精简,这里就不展开了。

还有就是,UserPayServiceStrategyFactory.register 调用的时候,第一个参数需要传一个字符串,这里的话其实也可以优化掉。

比如使用枚举,或者在每个策略类中自定义一个 getUserType 方法,各自实现即可。

总结

本文,我们通过策略模式、工厂模式以及 Spring 的 InitializingBean,提升了代码的可读性以及可维护性,彻底消灭了一坨 if-else。

文中的这种做法,大家可以立刻尝试起来,这种实践,是我们日常开发中经常用到的,而且还有很多衍生的用法,也都非常好用。有机会后面再介绍。

其实,如果读者们对策略模式和工厂模式了解的话,文中使用的并不是严格意义上面的策略模式和工厂模式。

首先,策略模式中重要的 Context 角色在这里面是没有的,没有 Context,也就没有用到组合的方式,而是使用工厂代替了。

另外,这里面的 UserPayServiceStrategyFactory 其实只是维护了一个 Map,并提供了 Register 和 Get 方法而已,而工厂模式其实是帮忙创建对象的,这里并没有用到。

所以,读者不必纠结于到底是不是真的用了策略模式和工厂模式。而且,这里面也再扩展一句,所谓的 GOF 23 种设计模式,无论从哪本书或者哪个博客看,都是简单的代码示例,但是我们日常开发很多都是基于 Spring 等框架的,根本没办法直接用的。

所以,对于设计模式的学习,重要的是学习其思想,而不是代码实现!!!希望通过这样的文章,读者可以真正的在代码中使用上设计模式。



Tags:代码   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言几乎所有.NET序列化程序的实现基础都是反射。下列代码是Newtonsoft.Json的实现:protectedvirtualJsonPropertyCreateProperty(MemberInfomember,MemberSerializationmemb...【详细内容】
2021-12-28  Tags: 代码  点击:(2)  评论:(0)  加入收藏
在SEO优化中,最重要的评估之一就是确定网站上存在哪些HTTP状态代码。这些代码可能会变得很复杂,成为一个难题,必须先解决这些难题,然后才能完成其他任务。例如,如果你放置的页面...【详细内容】
2021-12-24  Tags: 代码  点击:(5)  评论:(0)  加入收藏
1、通过条件判断给变量赋值布尔值的正确姿势// badif (a === &#39;a&#39;) { b = true} else { b = false}// goodb = a === &#39;a&#39;2、在if中判断数组长度不为零...【详细内容】
2021-12-24  Tags: 代码  点击:(6)  评论:(0)  加入收藏
前言本文提供将视频调整分辨率的Python代码,一如既往的实用主义。环境依赖ffmpeg环境安装,可以参考我的另一篇文章: windows ffmpeg安装部署_阿良的博客-CSDN博客ffmpy安装:pip...【详细内容】
2021-12-14  Tags: 代码  点击:(15)  评论:(0)  加入收藏
大家好, 我是林路,今天就给大家介绍Python代码都是用的什么编辑器写的?Jupyter Notebook ,没有Pycharm,没有Vscode,没有Sublime text。 只有一款工具:Jupyter Notebook 。工欲善其...【详细内容】
2021-12-09  Tags: 代码  点击:(27)  评论:(0)  加入收藏
在这篇文章中,我将与你分享一些关于JS的技巧,可以提高你的JS技能。1.避免if过长如果判断值满足多个条件,我们可能会这么写:if (value === &#39;a&#39; || value === &#39;b&#39;...【详细内容】
2021-11-17  Tags: 代码  点击:(22)  评论:(0)  加入收藏
一、为什么需要使用内存池在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;另一...【详细内容】
2021-11-17  Tags: 代码  点击:(38)  评论:(0)  加入收藏
《开源精选》是我们分享Github、Gitee等开源社区中优质项目的栏目,包括技术、学习、实用与各种有趣的内容。本期推荐的是一个由百度开源的低代码前端框架&mdash;&mdash;amis...【详细内容】
2021-11-05  Tags: 代码  点击:(68)  评论:(0)  加入收藏
程序员是青春饭,这在国内似乎是公认的。所以很多公司不愿招大龄程序员,很多程序员也“知趣”地及早转型。有的做管理,有的做架构,我还见过改行卖保险的。总之,年龄大了不想敲代码...【详细内容】
2021-10-27  Tags: 代码  点击:(30)  评论:(0)  加入收藏
我们来看看我们拨号键盘除了能打电话还能干什么iphone 的拨号键盘除了用来拨号,其实暗藏代码输入星井06井可以查询手机真实的IMEI码,这个码是独一无二的没有双胞胎 输入星3001...【详细内容】
2021-10-25  Tags: 代码  点击:(78)  评论:(0)  加入收藏
▌简易百科推荐
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(3)  评论:(0)  加入收藏
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(2)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(10)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(20)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(25)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(25)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条