如何去除If,else,switch条件判断
对于具有一定复杂逻辑的代码实现,避免不了出现if,else,switch等逻辑判断。当逻辑分支越来越多的时候,大大地加大了阅读的难度。这种情况,我们该如何处理呢?
对同一个变量的不同值作条件判断时,可以用switch语句与if语句,哪个语句执行效率更高呢,答案是switch语句,尤其是判断的分支越多越明显。(具体测试的代码,小伙伴可以试一下)
public static void main(String[] args) {
testIF("12");
testSwitch("12");
}
public static void testIF(String arg) {
long t1 = System.nanoTime();
if ("1".equals(arg)) {
System.out.println(arg);
} else if ("2".equals(arg)) {
System.out.println(arg);
} else if ("3".equals(arg)) {
System.out.println(arg);
} else if ("4".equals(arg)) {
System.out.println(arg);
} else if ("5".equals(arg)) {
System.out.println(arg);
} else if ("6".equals(arg)) {
System.out.println(arg);
} else if ("7".equals(arg)) {
System.out.println(arg);
} else if ("8".equals(arg)) {
System.out.println(arg);
} else if ("9".equals(arg)) {
System.out.println(arg);
} else if ("10".equals(arg)) {
System.out.println(arg);
} else if ("11".equals(arg)) {
System.out.println(arg);
} else if ("12".equals(arg)) {
System.out.println(arg);
} else if ("13".equals(arg)) {
System.out.println(arg);
} else if ("14".equals(arg)) {
System.out.println(arg);
} else {
System.out.println(arg);
}
long t2 = System.nanoTime();
System.out.println("test if : " + (t2 - t1));
}
public static void testSwitch(String arg) {
long t1 = System.nanoTime();
switch (arg) {
case "1":
System.out.println(arg);
break;
case "2":
System.out.println(arg);
break;
case "3":
System.out.println(arg);
break;
case "4":
System.out.println(arg);
break;
case "5":
System.out.println(arg);
break;
case "6":
System.out.println(arg);
break;
case "7":
System.out.println(arg);
break;
case "8":
System.out.println(arg);
break;
case "9":
System.out.println(arg);
break;
case "10":
System.out.println(arg);
break;
case "11":
System.out.println(arg);
break;
case "12":
System.out.println(arg);
break;
case "13":
System.out.println(arg);
break;
case "14":
System.out.println(arg);
break;
default:
System.out.println(arg);
break;
}
long t2 = System.nanoTime();
System.out.println("test switch: " + (t2 - t1));
}
最终现实结果
12
test if : 482713
12
test switch: 24870
复杂!复杂!代码圈复杂度高!
什么是代码圈复杂度?圈复杂度
1)概念:
- 用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。
1)计算公式:
- 计算公式为:V(G)=e-n+2。其中,e表示控制流图中边的数量,n表示控制流图中节点的数量。 其实,圈复杂度的计算还有更直观的方法,因为圈复杂度所反映的是“判定条件”的数量,所以圈复杂度实际上就是等于判定节点的数量再加上1,也即控制流图的区域数,对应的计算公式为:V(G)=区域数=判定节点数+1。
- 对于多分支的CASE结构或IF-ELSE结构,统计判定节点的个数时需要特别注意一点,要求必须统计全部实际的判定节点数,也即每个 ELSEIF语句,以及每个CASE语句,都应该算为一个判定节点。判定节点在模块的控制流图中很容易被识别出来,所以,针对程序的控制流图计算圈复杂度 V(G)时,最好还是采用第一个公式,也即V(G)=e-n+2;而针对模块的控制流图时,可以直接统计判定节点数,这样更为简单。
interface ILog {
void log();
}
class FileLog implements ILog {
public void log() {
}
}
class ConsoleLog implements ILog {
public void log() {
}
}
class NullObjectLog implements ILog {
public void log() {
}
}
public class LogFactory {
static ILog Create(String str) {
ILog log = new NullObjectLog();
if ("file".equals(str))
log = new FileLog();
if ("console".equals(str))
log = new ConsoleLog();
return log;
}
}
观察下面的一维数组的形式可以发现,定义了2个变量。
例如:int a[12],a[x]=y;
相当于函数 y=f(x) (在此例子中,x和y为均为int类型),于是就变成了我们平时熟悉的普通的c函数。而函数一般通过数学表达式和逻辑判断的形式得出结果,而这样的结果一般的来说有数学规律,例如像n!就很适合于使用函数实现。
而表驱动法的函数关系是人为定义的,如果采用函数,一般会出现很多的if、else判断。所以表驱动法适合于去实现“人造逻辑”的函数。
enum SexStatus {
Female, Male
}
enum MaritalStatus {
Single, Married
}
enum SmokingStatus {
NonSmoking, Smoking
}
public double ComputeInsuranceCharge(SexStatus sexStatus, MaritalStatus maritalStatus, SmokingStatus smokingStatus, int age) {
double rate = 1;
if (sexStatus.equals(SexStatus.Female)) {
if (maritalStatus.equals(MaritalStatus.Single)) {
if (smokingStatus.equals(SmokingStatus.NonSmoking)) {
if (age < 18) {
rate = 40.00;
} else if (age == 18) {
rate = 42.50;
} else if (age == 19) {
rate = 45.00;
}
...
else if (age > 65) {
rate = 150.00;
}
} else if (smokingStatus == SmokingStatus.Smoking) {
if (age < 18) {
rate = 44.00;
} else if (age == 18) {
rate = 47.00;
} else if (age == 19) {
rate = 50.00;
}
...
else if (age > 65) {
rate = 200.00;
}
}
} else if (maritalStatus == MaritalStatus.Married) {
//......
}
}
return rate;
}
但是仔细看一下代码,其实保险费率和性别、婚姻、是否抽烟、年龄这个几个因素有一定的关系,尤其年龄的变化区间是相当大,按照上述的写法,可想而知,代码的复杂会达到什么样子的程度。
这时候,肯定有人会想不需要对每个年龄进行判断,而且将保险费用放入年龄数组中,这样将极大地改进上述的代码。不过,如果把保险费用放入所有影响因素的数组而不仅仅是年龄数组的话,将会使程序更简单,类似于可以设计一个费率表格,来降低代码的复杂度呢。
Table<Integer, RateFactor, Double> rateTable = HashBasedTable.create();
enum RateFactor {
MALE_SINGLE_NONSMOKING,
MALE_SINGLE_SMOKING,
MALE_MARRIED_NONSMOKING,
MALE_MARRIED_SMOKING,
FEMALE_SINGLE_NONSMOKING,
FEMALE_SINGLE_SMOKING,
FEMALE_MARRIED_NONSMOKING,
FEMALE_MARRIED_SMOKING,
}
public double ComputeInsuranceCharge(RateFactor rateFactor, int age) {
int ageFactor;
if (age < 18) {
ageFactor = 0;
} else if (age > 65) {
ageFactor = 65 - 17;
} else {
ageFactor = age - 17;
}
return rateTable.get(ageFactor, rateFactor);
}
原先代码:
switch (something) {
case 1:
doX();
break;
case 2:
doY();
break;
case 3:
doN();
break;
// And so on...
}
重构后代码:
var cases = {
1: doX,
2: doY,
3: doN
};
if (cases[something]) {
cases[something]();
}
关于表驱动法,还是其他灵活的使用方法,可以参考《代码大全》
使用继承子类的多态,它们使用对象的间接性有效地摆脱了传统的状态判断。
使用继承子类多态的方式,通常对于某个具体对象,它的状态是不可改变的(在对象的生存周期中)。
public class Method {
private int type;
public static final int POST = 0;
public static final int GET = 1;
public static final int PUT = 2;
public static final int DELETE = 3;
public Method(int type) {
this.type = type;
}
public String getMethod() throws RuntimeException {
switch (type) {
case POST:
return "这是 POST 方法";
case GET:
return "这是 GET 方法";
case PUT:
return "这是 PUT 方法";
case DELETE:
return "这是 DELETE 方法";
default:
throw new RuntimeException("方法类型调用出错");
}
}
}
如果希望对象在生存周期内,可以变化自己的状态,则可以选择state模式。
重构方法:这里抽象状态为一个接口MethodType,四种不同的状态实现该接口。