前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >对复杂if-else代码块的优化方案

对复杂if-else代码块的优化方案

作者头像
冬天里的懒猫
发布2021-01-29 11:11:05
9760
发布2021-01-29 11:11:05
举报
文章被收录于专栏:做不甩锅的后端

文章目录

1.1 问题提出

对于很多码农而言,if-else可能是最高频的代码关键字,毕竟,这也比较符合人们二维思考问题的方式,试想大部分问题的答案都是只有两个维度,要么true,要么false,那么通过if-else的方式是再好不过了。当然,if-else固然好,但是在代码中过多的使用,或者反复的嵌套使用,那样就不好了。 前几天看到了下面这张图,固然这张图比较夸张,但是也说明了,多重嵌套的if-else的不可取之处。

image.png
image.png

今天本文就来聊聊,在java中,面对已经出现了的多重if-else嵌套的情况,我们应该怎么去优化。

考虑到要优化if,else的方案,那么现在正好手头上有一个具体的实例代码,在netty的自定义协议栈中,在netty收到消息之后的ByteToMessageDecoder中,将收到的二进制消息,转换为所需要的实体对象。

代码语言:javascript
复制
if(in.readInt() == 1) {
	//转换为TankJoinMsg对象

}else if(in.readInt() == 2) {
	//转换为TankStartMovingMsg对象

}else if(in.readInt() == 3) {
	//转换为TankStopMsg对象

}else if(in.readInt() == 4) {
	//转换为TankDirChangedMsg对象

}else if(in.readInt() == 5) {
	//转换为BulletNew对象

}

代码结构如上所示,现在需要在channel中对传入的第一个int字段进行判断,根据这个字段的值,来确定传入的数据类型,之后将后续的字节流转换为所需要的实体对象。这个过程非常low。

1.2 用switch-case优化

鉴于if-else的控制逻辑的冗余性,如果if-else的分支间不存在关联性,那么首先想到的解决方案是通过switch-case。对于本文的问题,可以定义一个枚举类MsgType,然后用switch-case来解决。 如下是定义的枚举类:

代码语言:javascript
复制
public enum  MsgType {
	TankJoin,TankDirChanged,TankStop,TankStartMoving,BulletNew,TankDie,TankExit
}

之前的if-else代码被优化为:

代码语言:javascript
复制
MsgType msgType = MsgType.values()[in.readInt()];
int length = in.readInt();
if (in.readableBytes() < length) {
	in.resetReaderIndex();
	return;
}
byte[] bytes = new byte[length];
in.readBytes(bytes);

Msg msg = null;
switch (msgType) {
	case TankJoin:
		msg = new TankJoinMsg();
		msg.parse(bytes);
		out.add(msg);
		break;
	case TankStartMoving:
		msg = new TankStartMovingMsg();
		msg.parse(bytes);
		out.add(msg);
		break;
	case TankStop:
		msg = new TankStopMsg();
		msg.parse(bytes);
		out.add(msg);
		break;
	case TankDirChanged:
		msg = new TankDirChangedMsg();
		msg.parse(bytes);
		out.add(msg);
		break;
	case BulletNew:
		msg = new BulletNewMsg();
		msg.parse(bytes);
		out.add(msg);
		break;
	case TankDie:
		msg = new TankDieMsg();
		msg.parse(bytes);
		out.add(msg);
		break;
	case TankExit:
		msg = new TankExitMsg();
		msg.parse(bytes);
		out.add(msg);
		break;
	default:
		break;
}

这样就能很好的将if转换为了switch-case的方式。但是需要注意的是,并不是全部的if-esle的复杂逻辑都能转换为switch-case,只有if的分支处于并列关系,且分支逻辑间没有什么关联性的情况才适用这种情况。此外,上述switch-case方法还存在一个问题就是,一旦增加了新的消息类型,那么就需要不断的修改这个类的代码进行扩展。这在设计上来说不是一个很好的设计。这就不满足开闭原则。

1.3 用反射替换switch-case

对于上述switch-case逻辑,我们可以看到,是存在一定的规律的,我们定义的消息类型,如TankJoin,则会处理为TankJoinMsg对象来进行处理。那么只要我们后续添加的类型都始终满足这个逻辑的话,我们就可以使用反射的方式来优化这部分代码,使其符合开闭原则。

代码语言:javascript
复制
Msg msg = null;
//此处可用反射替换
Class localClass = Class.forName("com.dhb.tank.mode."+msgType.toString()+"Msg");
msg = (Msg)localClass.newInstance();
msg.parse(bytes);
out.add(msg);

这样就将上述复杂的switch代码通过反射几行代码就能搞定。 但是需要注意的是,反射代码存在的问题是,在写代码的时候需要满足一些通用的规则,如上述代码中,我们根据type的toString加上Msg字符串就能够反射出这个实体类,我们在增加新的业务类型的时候,就带来了局限性,所以通过反射方式来实现的逻辑的话,必须要将这写潜在的业务规则写明白,以便后续的开发者忽略了这些规则而造成bug。

1.4 策略模式进一步优化

如果要对反射的实现反射进一步优化的话,那么还可以使用策略模式来实现。 代码实现如下: 首先需要定义一个HashMap,将对应关系存在这个hashMap中。

代码语言:javascript
复制
private static final Map<MsgType,Msg> msgMap = new HashMap<>();
static{
	msgMap.put(MsgType.TankJoin,new TankJoinMsg());
	msgMap.put(MsgType.TankStartMoving,new TankStartMovingMsg());
	msgMap.put(MsgType.TankStop,new TankStopMsg());
	msgMap.put(MsgType.TankDirChanged,new TankDirChangedMsg());
	msgMap.put(MsgType.BulletNew,new BulletNewMsg());
	msgMap.put(MsgType.TankDie,new TankDieMsg());
	msgMap.put(MsgType.TankExit,new TankExitMsg());
}

之后使用这个代码就非常容易了:

代码语言:javascript
复制
Msg msg = null;
//此处可用反射替换
msg = msgMap.get(msgType);
msg.parse(bytes);
out.add(msg);

可以直接采用get的方式就能轻松或者之前定义的msg类型进行处理。如果在spring中,这个map完全可以在配置文件中进行配置,然后再此处使用的时候进行注入。那么就能完美实现减少代码的目的。 不过需要注意的是,上述方式仍然只能解决并列的分支判断问题。

1.5 用责任链模式处理复杂的嵌套关系

考虑到策略模式只能解决并列分支的问题,对解决分支嵌套的问题还是没有任何帮助。因此,我们考虑另外一种设计模式,责任链模式。责任链模式的链实际上是一个list对象,如果需要进入下一个嵌套,那么此处就不是写一个新的if-else,而是将这个新的if-else封装为一个对象,写在代码里面。 如果假定我们上述的if-else嵌套为如下的话:

代码语言:javascript
复制
if(in.readInt() == 1) {
	//转换为TankJoinMsg对象
    if(in.readInt() == 2) {
    //转换为TankStartMovingMsg对象
         if(in.readInt() == 3) {
    	//转换为TankStopMsg对象
            if(in.readInt() == 4) {
        	//转换为TankDirChangedMsg对象
                if(in.readInt() == 5) {
                	//转换为BulletNew对象
                    
                    //复杂嵌套式的处理逻辑

                    } else {
                       //处理逻辑5 
                    }
            }else {
                //else处理逻辑4
            }
        }else {
            //else处理逻辑3
        }
    }else {
        //else处理逻辑2
    }
}else {
    //else处理逻辑1
    
} 

这就与开篇那张图非常类似了,对于这样的嵌套逻辑,那么可以采用责任链模式进行优化。 上述代码修改为责任链如下: 构建了一个处理链:

代码语言:javascript
复制
public class ProcessChain {
	int index = 0;
	List<MsgProcesser> processers = new ArrayList<>();

	public ProcessChain add(MsgProcesser p) {
		processers.add(p);
		return this;
	}

	public void process(Msg msg) {
		if(index == processers.size()) {
			return;
		}
		MsgProcesser proccess = processers.get(index);
		index ++;
		proccess.process(msg,this);
	}
}

之后将每层的if-else都定义为一个msgProcesser

代码语言:javascript
复制
public interface MsgProcesser {

	public void process(Msg msg,ProcessChain chain);


}

然后具体的类实现这个processer:

代码语言:javascript
复制
public class TankJoinMsgProcesser implements MsgProcesser{

	@Override
	public void process(Msg msg, ProcessChain chain) {
		if(in.readInt() == 1) {
			//if处理逻辑,之后继续执行责任链中的后续逻辑
			chain.process(msg);
		}else {
			//else处理逻辑,并退出
		}
	}
}

可以看到,我们在处理具体的proccesser的实现类的时候,如果if逻辑满足,则继续对链中的后续逻辑进行调用。 那么在调用的时候,只需要将已经构造好的处理器增加到chain中,之后就能完成整个流程。

代码语言:javascript
复制
ProcessChain chain = new ProcessChain();
		chain.add(new TankJoinMsgProcesser()).add(new TankStartMovingMsgProcesser());
		...
		chain.process(msg);

其本质就是将每一层的if-else都转换为了一个具体的类,如果某个类里面如果需要继续向下嵌套,那么继续调用这个chain的process方法。 需要注意的是,这是一种单一的责任链,如果条件复杂的情况下,可能会构成多个链。 反正不难看出,对于if-else的处理,实际上有很多方式,但是我们需要注意的是避免对程序的过度设计,这样会造成代码的可读性变差。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/01/22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1.1 问题提出
  • 1.2 用switch-case优化
  • 1.3 用反射替换switch-case
  • 1.4 策略模式进一步优化
  • 1.5 用责任链模式处理复杂的嵌套关系
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档