平时我们在写代码时,需要针对不同情况处理不同的业务逻辑,用得最多的就是if和else。 但是如果情况太多,就会出现一大堆的“if else”,这就是为什么很多遗留系统中,一个函数可能出现上千行的代码。当然你说可以通过抽取方法或者类来实现,每一个情况交给一个方法或者对应一个类来处理,但是这样做只是看起来代码整洁了一些,还是有大量的”if else",后面有新的逻辑时,又要添加更多的“if else",没有从根本上解决问题。

举个例子,短信发送业务的实现,一般公司会接入多个短信供应商,比如梦网、玄武、阿里云等多个短信平台(我们称之为短信渠道),可能需要针对不同的短信类型或者短信平台的稳定性来切换短信渠道:

1.比如阿里云短信管控很严,带营销字样的短信不让发送,则营销类短信需要使用其他短信渠道来发送;

2.也有可能某个短信平台服务挂了暂时不可用,需要切换到另一个短信渠道;

3.某些短信平台有优惠,则需要临时切换到该短信渠道发送短信;

4.…

代码实现

上面的业务场景简单来说就是:针对不同的短信渠道来调用对应的短信平台接口实现短信发送。

短信渠道一般配置在文件中,或者配置在数据库中。

代码实现如下(注意下面所有的代码都不能直接运行,只是关键逻辑部分的示例代码):

烂代码示例

我们有一个短信发送类:SmsSendService,里面有一个send方法发送短信

SmsSendService.java

public class SmsSendService{
/**
* @Param phoneNo 手机号
* @Param content 短信内容
*/
public void send(String phoneNo,String content){
//从配置中读取 短信渠道
String channelType=config.getChannelType(); //如果是短信渠道A,则调用渠道A的api发送
if(Objects.equals(channelType,"CHANNEL_A")){
System.out.println("通过短信渠道A发送短信");
}
//如果是短信渠道B,则调用渠道B的api发送
else if(Objects.equals(channelType,"CHANNEL_B")){
System.out.println("通过短信渠道B发送短信");
}
}
}

  

如果某天增加了一个短信渠道C,那么接着追加一个”else if…"

//... 此处省略部分代码 ...

//从配置中读取 短信渠道
String channelType=config.getChannelType();
//如果是短信渠道A,则调用渠道A的api发送
if(Objects.equals(channelType,"CHANNEL_A")){
System.out.println("通过短信渠道A发送短信");
}
//如果是短信渠道B,则调用渠道B的api发送
else if(Objects.equals(channelType,"CHANNEL_B")){
System.out.println("通过短信渠道B发送短信");
}
//ADD: 如果是短信渠道C,则调用渠道C的api发送
else if(Objects.equals(channelType,"CHANNEL_C")){
System.out.println("通过短信渠道C发送短信");
} //... 此处省略部分代码 ...

  

如果又加其他短信渠道了呢?你又写一个“else if …" ?

显然这种做法不可取,也不符合SOLID原则中的”开闭原则“ ——对扩展开放,对更改封闭。

这样我们每次都需要修改原有代码(对更改没有封闭),不断的添加”if else"。

接下来我们把代码优化一下:

优化代码1

定义一个短信渠道的接口 SmsChannelService,所有的短信渠道API都实现该接口;

短信渠道接口 SmsChannelService.java

public interface SmsChannelService{
//发送短信
void send(String phoneNo,String content);
}

  

短信渠道A SmsChannelServiceImplA.java

public class SmsChannelServiceImplA implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通过短信渠道A发送短信");
}
}

  

短信渠道B SmsChannelServiceImplB.java

public class SmsChannelServiceImplB implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通过短信渠道B发送短信");
}
}

  

通过工厂类来初始化所有短信渠道service

SmsChannelFactory.java

public class SmsChannelFactory {
private Map<String,SmsChannelService> serviceMap; //初始化工厂,将所有的短信渠道Service放入Map中
public SmsChannelFactory(){
//渠道类型为 key , 对应的服务类为value :
serviceMap=new HashMap<String, SmsChannelService>(2);
serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB());
} //根据短信渠道类型获得对应渠道的Service
public SmsChannelService buildService(String channelType){
return serviceMap.get(channelType);
}
}

  

在原来的SmsSendService中调用不同短信渠道的接口。

原来的 SmsSendService 类优化如下

public class SmsSendService {

    private SmsChannelFactory smsChannelFactory;

    public SmsSendService(){
smsChannelFactory=new SmsChannelFactory();
} public void send(String phoneNo,String content){
//从配置中读取 短信渠道
String channelType=config.getChannelType();
//获取渠道类型对应的服务类
SmsChannelService channelService=smsChannelFactory.buildService(channelType);
//发送短信
channelService.send(phoneNo,content);
} }

  

这样SmsSendService类非常简洁,把“if else"干掉了,

如果我要增加一个短信渠道C,无需再次更改 SmsSendService 类。

只需要增加一个类 SmsChannelServiceImplC 实现 SmsChannelService 接口,

然后在工厂类 SmsChannelFactory 中增加一行初始化 SmsChannelServiceImplC 的代码即可。

增加短信渠道C的实现 SmsChannelServiceImplC.java

public class SmsChannelServiceImplC implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通过短信渠道C发送短信");
}
}

  

修改工厂类 SmsChannelFactory.java

public class SmsChannelFactory {
private Map<String,SmsChannelService> serviceMap; //初始化 serviceMap ,将所有的短信渠道Service放入Map中
public SmsChannelFactory(){
//渠道类型为 key , 对应的服务类为value :
serviceMap=new HashMap<String, SmsChannelService>(3);
serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB());
//ADD 增加一行 SmsChannelServiceImplC 的初始化代码
serviceMap.put("CHANNEL_C",new SmsChannelServiceImplC());
} //根据渠道类型构建短信渠道Service
public SmsChannelService buildService(String channelType){
return serviceMap.get(channelType);
}
}

  

“if else"是干掉了,但还是得修改原来的类 SmsChannelFactory ,不满足"开闭原则",有没有更好得方式呢?

我们通过使用spring的依赖注入进一步优化代码:

优化代码2

SmsChannelService 接口增加 getChannelType() 方法,这一步很关键。

public interface SmsChannelService {
//发送短信
void send(String phoneNo,String content);
//关键:增加getChannelType()方法,子类实现这个方法用于标识出渠道类型
String getChannelType();
}

  

子类增加该方法的实现,并加上 @Service 注解,使其让spring容器管理起来

SmsChannelServiceImplA.java

@Service
public class SmsChannelServiceImplA implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通过短信渠道A发送短信");
}
//关键:增加 getChannelType() 实现
public String getChannelType() {
return "CHANNEL_A";
}
}

  

SmsChannelServiceImplB.java

@Service
public class SmsChannelServiceImplB implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通过短信渠道B发送短信");
}
//关键:增加 getChannelType() 实现
public String getChannelType() {
return "CHANNEL_B";
}
}

  

修改 SmsChannelFactory 类: 这一步也很关键。

SmsChannelFactory.java

@Service
public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; /*注入:通过spring容器将所有实现 SmsChannelService 接口的类的实例注入到 serviceList 中*/
@Autowired
private List<SmsChannelService> serviceList; /*通过 @PostConstruct 注解,在 SmsChannelFactory 实例化后,来初始化 serviceMap */
@PostConstruct
private void init(){
if(CollectionUtils.isEmpty(serviceList)){
return ;
}
serviceMap=new HashMap<String, SmsChannelService>(serviceList.size());
//将 serviceList 转换为 serviceMap
for (SmsChannelService channelService : serviceList) {
String channelType=channelService.getChannelType();
//重复性校验,避免不同实现类的 getChannelType() 方法返回同一个值。
if(serviceMap.get(channelType)!=null){
throw new RuntimeException("同一个短信渠道只能有一个实现类");
}
/*渠道类型为 key , 对应的服务类为value :
与“优化代码1”中的通过手工设置“CHANNEL_A"、"CHANNEL_B"相比,
这种方式更加自动化,后续在增加“CHANNEL_C"无需再改此处代码*/
serviceMap.put(channelType,channelService);
}
} //根据渠道类型获取对应短信渠道的Service
public SmsChannelService buildService(String channelType){
return serviceMap.get(channelType);
}
}

  

SmsSendService 加上 @Service 注解。通过 @Autowired 注入 SmsChannelFactory

SmsSendService.java

@Service
public class SmsSendService { @Autowired
private SmsChannelFactory smsChannelFactory; public void send(String phoneNo,String content){
//从配置中读取短信渠道类型
String channelType=config.getChannelType();
//构建渠道类型对应的服务类
SmsChannelService channelService=smsChannelFactory.buildService(channelType);
//发送短信
channelService.send(phoneNo,content);
} }

  

这时,如果需要添加一个渠道C,那真的只需要添加一个 SmsChannelServiceImplC 即可,再也不用改原有代码,完全遵循“开闭原则”。

SmsChannelServiceImplC.java

@Service
public class SmsChannelServiceImplC implements SmsChannelService {
public void send(String phoneNo, String content) {
System.out.println("通过短信渠道C发送短信");
} public String getChannelType() {
return "CHANNEL_C";
}
}

  

总结

通过上述优化很好的去掉了 “if else" ,再也不会出现”又臭又长“像”卫生卷纸"一样的代码了,而且完全遵循”开闭原则"。

spring是个好东西,关键看你怎么用。

看完有什么不懂的欢迎在下方留言评论,记得点个赞哦!

深度解析:如何替换掉代码中的ifelse,我女朋友看完都会了!的更多相关文章

  1. Kafka深度解析(如何在producer中指定partition)(转)

    原文链接:Kafka深度解析 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅的消息系统.主要设计目标如下: 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能 ...

  2. 【转】消除代码中的 if-else/switch-case

    在很多时候,我们代码中会有很多分支,而且分支下面的代码又有一些复杂的逻辑,相信很多人都喜欢用 if-else/switch-case 去实现.做的不好的会直接把实现的代码放在 if-else/swit ...

  3. 如何减少代码中的if-else嵌套

    实际项目中,往往有大量的if-else语句进行各种逻辑校验,参数校验等等,大量的if-else,语句使代码变得臃肿且不好维护,本篇文章结合我自己的经验,就减少if-else语句给出以下几种方案,分别适 ...

  4. 深度分析:java设计模式中的原型模式,看完就没有说不懂的

    前言 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的 ...

  5. lua中怎么替换掉字符串中的$^特殊字符?

    Lua 常规替换字符串如何替换 s = string.gsub("Lua is good", "good", "bad") print(s) ...

  6. 转:Uncovering Drupalgeddon 2(cve-2018-7600)漏洞深度解析(附漏洞利用代码地址)

    转:https://research.checkpoint.com/uncovering-drupalgeddon-2/ By Eyal Shalev, Rotem Reiss and Eran Va ...

  7. 深度解析标点符号在Report写作中的应用

    准确的标点符号和大写字母可以帮助Tutor准确理解report的意思.标点符号的某些方面,例如使用逗号,可以是一种个人风格,在引号中正确的标点符号是至关重要的.在前面的一些文章当中我们也给大家说了re ...

  8. 利用设计模式消除业务代码中的 if-else

    准备工作:假设这样的一个业务场景:有一个自动开票的功能需要实现,在程序里面需要根据账单的类型执行对应的处理逻辑. 以下使用了 Lombok 简化代码!!! 账单类型枚举: /** * @author ...

  9. 深度分析:Java并发编程之线程池技术,看完面试这个再也不慌了!

    线程池的好处 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池,相对于单线程串行处理(Serial Processing ...

随机推荐

  1. Linux运维学习第一周记

    1 当年白岳伴清游, 2 江石台空一苇浮. 3 缥渺临风闻郢曲, 4 殷勤歧路看吴钩. 老气横秋方知世间沧桑! 以前一直忙,没有时间沉浸下来学习,一直都是浮着. 至此大疫,给生命按下了暂停键. 踏踏实 ...

  2. Python入门教程完整版(懂中文就能学会)

    前几天给大家分享<从零学会Photoshop经典教程300集>的教程受到了广泛的关注,有人不知道怎么领取,居然称小编为"骗子". 不过小编的内心是强大的,网友虐我千百遍 ...

  3. CocoaFoundation和CoreFoundation之间数据转换(桥接__bridge)

    ARC仅管理Objective-C指针(retain.release.autorelease),不管理CoreFoundation指针,CF指针由人工管理,手动的CFRetain和CFRelease来 ...

  4. *新阿里镜像maven配置流程

    解决idea中maven仓库下载jar包慢的问题 配置阿里镜像 设置idea 配置阿里镜像参考链接:https://blog.csdn.net/redhat0921/article/details/1 ...

  5. Kubernetes 配置私有镜像仓库时,没有权限访问的问题

    使用 K8S 部署服务时,如果指定的镜像地址是内部镜像仓库,那么在下载镜像的时候可能会报权限错误.这是由于在 K8S 中部署服务时,K8S 需要到 Harbor 中进行一次验证,这个验证与节点中使用 ...

  6. 用 Java 训练深度学习模型,原来可以这么简单!

    本文适合有 Java 基础的人群 作者:DJL-Keerthan&Lanking HelloGitHub 推出的<讲解开源项目> 系列.这一期是由亚马逊工程师:Keerthan V ...

  7. NOIP 2013 P1967 货车运输

    倍增求LCA+最大生成树 题目给出的是一张图,在图上有很多算法无法实现,所以要将其转化为树 题中可以发现货车的最后的载重量是由权值最小的一条边决定的,所以我们求最大生成树 求完最大生成树后我们得到一个 ...

  8. Python中的小括号()、中括号[]、花括号{}区别

    Python中最常见括号的区别: 在Python语言中最常见的括号有三种,分别是:小括号().中括号[].花括号{}:其作用也不相同,分别用来代表不同的Python基本内置数据类型. Python中的 ...

  9. Android Google官方文档解析之——Application Fundamentals

    Android apps are written in the java programming language.The Android SDK tools compile your code-al ...

  10. 白话科普系列——最好用的浏览器 Chrome,你用了么?

    市面上的浏览器多种多样,而浏览器的王者--IE 浏览器,它在 2002 年市场份额高达 95.4%.直到后续 Firefox,Safari,Chrome 相继问世才动摇了 IE 浏览器的地位,其中 C ...