else 策略模式去掉if_业务复杂=if else?刚来的大神竟然用策略+工厂彻底干掉了他们!...

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

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

79e7f9d2eec6ca711c6f09aa9b52840a.png

首先可读性,不言而喻,过多的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 {@Overridepublic BigDecimal quote(BigDecimal orderPrice) {if (消费金额大于30元) {return 7折价格;}}
}public class SuperVipPayService implements UserPayService {@Overridepublic BigDecimal quote(BigDecimal orderPrice) {return 8折价格;}
}public class VipPayService implements UserPayService {@Overridepublic 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 {@Overridepublic BigDecimal quote(BigDecimal orderPrice) {if (消费金额大于30元) {return 7折价格;}}@Overridepublic void afterPropertiesSet() throws Exception {UserPayServiceStrategyFactory.register("ParticularlyVip",this);}
}@Service
public class SuperVipPayService implements UserPayService ,InitializingBean{@Overridepublic BigDecimal quote(BigDecimal orderPrice) {return 8折价格;}@Overridepublic void afterPropertiesSet() throws Exception {UserPayServiceStrategyFactory.register("SuperVip",this);}
}@Service  
public class VipPayService implements UserPayService,InitializingBean {@Overridepublic BigDecimal quote(BigDecimal orderPrice) {if(该用户超级会员刚过期并且尚未使用过临时折扣){临时折扣使用次数更新();returen 8折价格;}return 9折价格;}@Overridepublic 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等框架的,根本没办法直接用的。

所以,对于设计模式的学习,重要的是学习其思想,而不是代码实现!!!

如果读者们感兴趣,后续可以出更多的设计模式和Spring等框架结合使用的最佳实践。希望通过这样的文章,读者可以真正的在代码中使用上设计模式。
作者:漫话编程
链接:https://juejin.im/post/5dad23685188251d2c4ea2b6
来源:掘金

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/527069.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

e.html5.qq.com,QQ群官网-开放能力

8. 设置提醒组件8.1 功能说明应用可以调用该组件设置日历提醒8.2 组件说明组件使用365日历提供的日历服务在需要使用的页面上嵌入js然后在需要的地方如下调用即可window.openGroup.setReminder({appid: 101234888, // 选填&#xff0c;若不填则使用url参数appid(必须有)// 当前…

istio springcloud_手牵手一起学Springcloud(1)微服务这么流行,你理解了嘛?

在前一段时间&#xff0c;我们实验室的项目开始变得越来越麻烦&#xff0c;代码也越来越臃肿&#xff0c;一个人兼顾前后端的全栈开发&#xff0c;实在是力不从心&#xff0c;没有一点点幸福感&#xff0c;于是迫切的想要解放生产力&#xff0c;放飞自我&#xff0c;因此开始决…

2021年计算机应用基础统考题库,2021年计算机应用基础统考题库试卷全-20210515145621.doc-原创力文档...

一、单选题1、第三代计算机运算速度_____。A&#xff1a;高于第二代但低于第四代B&#xff1a;高于第一代但低于第二代C&#xff1a;低于第一代D&#xff1a;高于第四代答案&#xff1a; A2、将计算机分为通用计算机、专用计算机两类分类原则是______。A&#xff1a;计算机解决…

力改变物体形状举例_人教版八年级物理下册第七章《力》知识点大全

力是物体对物体的作用&#xff0c;比如推土机推动了土。知识点1&#xff1a;力1.概念&#xff1a;是物体对物体的作用叫做力。2.特点&#xff1a;物体间力的作用是相互的。3.力的单位和表示符号&#xff1a;(1)力的单位&#xff1a;牛顿&#xff0c;简称牛(N)。托起一个鸡蛋大约…

计算机29首流行音乐叫什么,流行歌曲有哪些 流行歌曲500首

流行歌曲有哪些 流行歌曲500首作者: 锦瑟更新日期: 2020-10-15 21:43:12音乐是一种能治愈人心的存在&#xff0c;它还可以炒热气氛和渲染气氛&#xff0c;总之歌曲的共情能力是很强的&#xff0c;相信没几个人会不喜欢音乐的吧。小编也喜欢在无聊的时候听一下音乐呢&#xff0c…

bool类型数组转换成一个整数_Go 学习笔记 02 | 基本数据类型以及 byte 和 rune 类型...

一、基本数据类型unsafe.Sizeof() 查看不同长度的整型在内存中的存储空间。 类型转换&#xff0c;高位向低位转换要注意溢出。数字字面量语法。64 位系统中 Go 语言中浮点数默认是 float64。二、Golang 中 float 精度丢失问题利用第三方包&#xff1a;http://github.com/shopsp…

计算机应用基础(高起专)答案,东北师范大学14秋《计算机应用基础(高起专)》14秋在线作业1答案...

计算机应用基础(高起专)《计算机应用基础(高起专)》14秋在线作业1 一,单选题1. 局域网组网完成后&#xff0c;决定网络使用性能的关键是A. 网络的拓扑结构B. 网络的通信协议C. 网络的传输介质D. 网络的操作系统?正确答案&#xff1a;D2. 加工处理汉字信息时&#xff0c;使用汉…

ldap基本dn_LDAP 中 DN CN DC OU

DN 的英文名称是&#xff08;distinguished name&#xff09;&#xff0c;直接翻译过来就是专有名称。简单的就可以理解为一个路径就对了。这个路径可以指向 OU &#xff0c;也可以指到 CN。其中 DN 有三个属性&#xff0c;分别是CN&#xff0c;OU&#xff0c;DC。DC (Domain C…

学校计算机 电教材料账册,电教室管理制度

电教室管理制度(一)电教室必须有专人管理。未经许可&#xff0c;外人不得擅 自动用仪器、设备。(二)按配备标准和教学要求&#xff0c;及时申购仪器、设备及材料&#xff0c;保证教学正常进行。(三)仪器、设备、材料入库&#xff0c;要凭单据及时记账、编号后分类存放&#xff…

滴答定时器的计数模式_【高手私藏】STM32学习笔记:SysTick滴答时钟

今天我们来说说SysTick定时器。SysTick定时器在从参考手册中根本没有介绍。我费了九牛二虎之力才在一个犄角格拉里找到SysTick定时器的英文版的说明。在Cotex-M3有介绍&#xff0c;为什么要找STM32的介绍&#xff0c;是因为功能设置上还有点区别。首先看一下SysTick定时器的作用…

2008wsus创建和管理计算机组,Windows Server 2012 R2 WSUS-6:配置计算机组和客户端目标...

转载&#xff1a;https://blog.51cto.com/543925535/1406660对于WSUS来说&#xff0c;配置计算机的方式有两个出发点&#xff0c;一个是使用update services控制台来配置计算机组&#xff0c;计算机分组的管理都需要手动维护&#xff0c;第二种是使用计算机上的组策略和注册表设…

多选框实现全选_Angular1.x-checkbox-全选amp;单选amp;多选

ng-checked&#xff1a;Angular里ng-checked属性影响复选框的状态&#xff0c;值>true则复选框选中&#xff0c;值>false则取消选中。HTML:<div class"col-lg-4"><fieldset><legend>选题列表</legend><div class"table-respon…

复旦计算机考研复试要口试吗,2017复旦大学考研复试:英语口语面试常见问题汇总...

2017复旦大学考研复试&#xff1a;英语口语面试常见问题汇总本站小编 辅仁网/2017-12-29A magazine publisher is trying to decide how many magazines she should deliver to each individual distribution out let in order to maximize profits. She has extensive histori…

2020笔记本性价比之王_什么笔记本性价比高?2020性价比最高的笔记本电脑

阅读本文前&#xff0c;请您先点击上面的蓝色字体&#xff0c;再点击“关注”&#xff0c;这样您就可以继续免费收到最新文章了。每天都有分享。完全是免费订阅&#xff0c;请放心关注。注&#xff1a;本文转载自网络&#xff0c;不代表本平台立场&#xff0c;仅供读者参考&…

专利计算机存储介质是智力活动,涉及计算机程序的发明专利申请的相关问题PPT课件.pptx...

文档介绍&#xff1a;1提纲涉及计算机程序的专利申请涉及算法的专利申请“方法与产品相对应”的情形2涉及计算机程序的专利申请涉及程序本身的权利要求主题名称为程序限定内容涉及程序本身限定内容仅涉及程序本身限定内容一部分涉及程序本身3涉及计算机程序的专利申请主题名称为…

前端请求接口post_接口自动化测试-WEB资讯专栏-DMOZ中文网站分类目录

为什么UI自动化维护成本更高&#xff1f;因为前端页面变化太快&#xff0c;而且UI自动化比较耗时(比如等待页面元素的加载、添加等待时间、定位元素、操作元素、模拟页面动作这些都需要时间)为什么接口自动化维护成本较低&#xff1f;因为接口较稳定&#xff0c;接口的响应时间…

input css年月日,input标签的type为date,显示的日期格式样式更改

这个///是改不了---的&#xff0c;这是谷哥自带的功能样式&#xff0c;只能改颜色背景色等&#xff0c;如果要那种效果可以用日历插件有个取巧的方法&#xff0c;一个不能改的input覆盖在input type"date"上面*{margin: 0;padding: 0;}#div{width: 500px;height: 300…

docker 容器安装conposer_docker和php:将依赖项(composer)放入容器中

目前我正致力于以下解决方案&#xff1a;开发环境&#xff1a;将整个src添加为volume&#xff1a;volumes:- .:/appinitial composer install commanddocker-compose exec app composer installinstall new composer package:docker-compose exec app composer require some/pa…

Windows虚拟服务器vm,史上最详细的虚拟机VMware12安装Windows7教程 | 心塞塞

首先你电脑必须安装了 VMware &#xff0c;推荐版本 VMware12 或者 VMware 11 版本&#xff0c;然后你还需要一个系统镜像&#xff0c;可以通过下面链接下载 Win7 的镜像&#xff0c;复制链接&#xff0c;打开迅雷新建任务即可下载&#xff1a;Windows7 64位1ed2k://|file|cn_w…

shader 获取法线_Unity Shader 入门到改行5——法线贴图

the best of blur1. 法线贴图理论1.1 什么是法线贴图一般的贴图中存储的是表面颜色值(RGBA)&#xff0c;而法线贴图存放的则是法线信息(xyzw)&#xff0c;假设某顶点处的 uv 坐标为 (u,v), 那么在法线贴图 (u,v)处纹素的值表示该顶点的“法线”方向。通常法线贴图中存储的并不是…