热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

如何优雅的替换掉Java代码中的ifelse

这篇文章主要介绍了如何优雅的替换掉Java代码中的ifelse,帮助大家优化自己的Java代码,提高可读性与简洁性,感兴趣的朋友可以了解下

场景

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

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

比如阿里云短信管控很严,带营销字样的短信不让发送,则营销类短信需要使用其他短信渠道来发送;
也有可能某个短信平台服务挂了暂时不可用,需要切换到另一个短信渠道;
某些短信平台有优惠,则需要临时切换到该短信渠道发送短信;

代码实现

上面的业务场景简单来说就是:针对不同的短信渠道来调用对应的短信平台接口实现短信发送。
短信渠道一般配置在文件中,或者配置在数据库中。

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

烂代码示例

我们有一个短信发送类: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 serviceMap;

	//初始化工厂,将所有的短信渠道Service放入Map中
	public SmsChannelFactory(){
		//渠道类型为 key , 对应的服务类为value :
		serviceMap=new HashMap(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 serviceMap;

	//初始化 serviceMap ,将所有的短信渠道Service放入Map中
	public SmsChannelFactory(){
		//渠道类型为 key , 对应的服务类为value :
		serviceMap=new HashMap(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 serviceMap;
	
	/*注入:通过spring容器将所有实现 SmsChannelService 接口的类的实例注入到 serviceList 中*/
	@Autowired
	private List serviceList;

	/*通过 @PostConstruct 注解,在 SmsChannelFactory 实例化后,来初始化 serviceMap */
	@PostConstruct
	private void init(){
		if(CollectionUtils.isEmpty(serviceList)){
			return ;
		}
		serviceMap=new HashMap(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";
	}
}

以上就是如何优雅的替换掉Java代码中的if else的详细内容,更多关于替换代码中的if else的资料请关注其它相关文章!


推荐阅读
  • Java EE CDI:解决依赖关系冲突的实例
    在本教程中,我们将探讨如何在Java EE的CDI(上下文和依赖注入)框架中有效解决依赖关系的冲突问题。通过学习如何使用限定符,您将能够为应用程序的不同客户端提供多种接口实现,并确保每个客户端都能正确调用其所需的实现。 ... [详细]
  • 精通C++并非易事,为何它比其他语言更难掌握?这主要归因于C++的设计理念,即不强迫用户接受特定的编程风格或限制创新思维。本文探讨了如何有效学习C++,并介绍了几本权威的学习资源。 ... [详细]
  • 使用Jenkins构建Java项目实践指南
    本指南详细介绍了如何使用Jenkins构建Java项目,包括环境搭建、工具配置以及项目构建的具体步骤。 ... [详细]
  • 本文将详细介绍Docker的网络架构,包括Docker自带的几种网络模式及其创建方法,探讨容器间及容器与外部世界的通信方式。此外,还将简要介绍单主机环境下的容器网络配置。 ... [详细]
  • 本文旨在介绍在iOS平台进行直播技术开发前的准备工作,重点讲解AVFoundation框架的基本概念和使用方法。通过对AVFoundation的深入理解,开发者能够更好地掌握直播应用中的音视频处理技巧。 ... [详细]
  • Python多线程编程详解
    本文深入探讨了Python中的多线程机制,包括线程的基本概念、创建线程的方法以及线程间的通信策略。 ... [详细]
  • 本文介绍了如何通过自定义配置类,利用 `WebMvcConfigurer` 接口来扩展 Spring MVC 的功能,实现默认首页的自动跳转,同时避免使用 `@EnableWebMvc` 注解全面接管 Spring MVC 的默认配置。 ... [详细]
  • 当在Windows环境下使用Docker运行容器时,如果忘记了添加-d参数,容器将以交互模式启动。本文将指导您如何安全地退出这种模式而不终止Docker容器。 ... [详细]
  • Go 通过 Map/Filter/ForEach 等流式 API 高效处理数据
    go,通过,map,filter,foreach,等,流,式,ap ... [详细]
  • 前端常用的布局类型——前端布局
    1.Static静态布局固定宽高:2.Liquid流式布局宽高用百分比,按屏幕分辨率调整,布局不发生变化3.Adaptive自适应 ... [详细]
  • 本文详细介绍了如何在Spring Boot项目中配置Maven的pom.xml文件,包括项目的基本信息、依赖管理及构建插件的设置。 ... [详细]
  • CSS Grid布局属性详解及媒体查询应用
    本文详细介绍了CSS Grid布局的各种属性,并探讨了如何利用Grid布局实现媒体查询功能,以适应不同设备的显示需求。 ... [详细]
  • JSP服务器概述及搭建指南
    本文详细介绍了JSP服务器的概念、主流服务器软件及其搭建步骤,旨在帮助开发者更好地理解和使用JSP技术。 ... [详细]
  • Docker入门与实践指南
    本文介绍了Docker的基础知识,包括其作为开源应用容器引擎的特点,以及如何利用Docker将应用程序及其依赖项打包成轻量级的容器镜像。同时,还详细讲解了Docker的核心概念、安装过程及基本命令操作。 ... [详细]
  • FFPlay 字幕与LRC歌词播放指南
    本文详细介绍了不同媒体容器支持的字幕格式,以及如何使用FFPlay和FFMPEG进行字幕和LRC歌词的播放与转换。涵盖的内容包括字幕显示方法、字体配置、字幕流选择等。 ... [详细]
author-avatar
军军CJJ_317
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有