java中冗余if-else代码块的优化(策略模式法,反射法)
JAVA中冗余if-else代码块的优化
开发中我们会经常写if(…) { } else if(…) {} else if (…) {}等多个逻辑判断,一般else if 会有好几个,
比如这样:
多个else if代码块会使得整个方法看起来比较臃肿,这篇文章的目的就是通过几种方式来减少甚至消灭else if。
优化冗余if-else代码块的三种方式
1.switch-case结构
在大多数文章中,有人指出使用switch替代if-else代码块。
switch()
{
case 1:
{
break;
}
case 2:
................
default:
............
}
其实代码长度和if-else差不多了,准确来说,if的应用比switch广泛,可以用switch的,都可以用if,但反之不行,因此这种方案局限性很大不推荐。
2.枚举法-策略模式。
3.反射法-映射机制。
上诉三种方式,下文的示例1,2,3,4都是在做场景还原和解决的思路,你可以直接跳到5.通过策略设计模式干掉if-else(方式一)的地方开始拿代码用。
1. 最简单的计算器实现(示例1)
首先以一个多if-else结构的计算器案例来还原场景。
比如现在有一个功能是计算器。:
class Untitled {
public static void main(String[] args) {
// 计算类型:1:加法 2:减法 3:乘法 4:除法
// 假设计算类型和计算的值都是正常客户端传过来的
int calcType = 1;
int num1 = 1;
int num2 = 3;
// 计算器实现
int result = 0;
if (calcType == 1) {
result = num1 + num2;
} else if (calcType == 2) {
result = num1 - num2;
} else if (calcType == 3) {
result = num1 * num2;
} else if (calcType == 4) {
result = num1 / num2;
}
System.out.println(result);
}
}
上面的代码实现一个数字的加减乘除功能,虽然目前代码看起来直接、简洁,但是,在实际开发过程中每种业务处理可能要几十行甚至更多行代码来完成,上诉只是4种类型的功能,假设每种类型的业务需要100行代码,这四种就会有400行代码,这只是if-else的代码量再加上该方法的其它代码可以想象该方法的代码量很大。
该示例代码有几个问题:
1.计算类型使用了 魔法值(即写死的值),程序中应该避免直接使用魔法值,我们可以使用枚举类来优化;
2.随着业务量的累计,我们的代码量会增大,因此我们需要控制方法的行数,一般通常控制到100行以内,所以可以通过将每个运算的具体实现都提取成一个单独的方法来优化,这样一来就缩小每个if-else的代码的行数。
2. 计算器功能单独封装成类(示例2)
在还原了一般多重if-else的代码场景后,我们开始进行优化。
- 增加一个枚举类CalcTypeEnum用于表示所有的运算类型, 使用枚举类来优化魔法值
- 将具体运算分别封装成单独的类中
采用枚举类的方式,将具体的功能单独封装到一个类中用于提供服务,因为服务方单独封装成一个独立的类,所以服务调用方代码就减少了很多,显得很清爽。
新建一个CalcTypeEnum.java枚举类:
public enum CalcTypeEnum {
ADD(1, "加法操作"),
SUB(2, "减法操作"),
MUL(3, "乘法操作"),
DIV(4, "除法操作"),
;
private Integer code;
private String description;
CalcTypeEnum(Integer code, String description) {
this.code = code;
this.description = description;
}
public Integer getCode() {
return code;
}
public String getDescription() {
return description;
}
}
/** 将具体的运算实现代码封装成独立的方法 */
public static int add(int num1, int num2) {
System.out.println("加法运算其它业务逻辑start");
System.out.println("此处省略几十行代码...");
System.out.println("加法运算其它业务逻辑end");
return num1 + num2;
}
public static int sub(int num1, int num2) {
return num1 - num2;
}
public static int mul(int num1, int num2) {
return num1 * num2;
}
public static int div(int num1, int num2) {
return num1 / num2;
}
}
新建一个Main.java类,模拟客户端来发出请求。
public class Main {
// 最后进行调用-Main方法你可以写到一个新的java文件中。
public static void main(String[] args) {
// 计算类型:1:加法 2:减法 3:乘法 4:除法
int calcType = 1;
int num1 = 1;
int num2 = 3;
int result = 0;
// 使用枚举来代替魔法值
if (CalcTypeEnum.ADD.getCode().equals(calcType)) {
// 假如加法运算中还包含其它业务逻辑,那么这些逻辑也被封装到方法中了,此处只有一行的代码量
result = add(num1, num2);
} else if (CalcTypeEnum.SUB.getCode().equals(calcType)) {
result = sub(num1, num2);
} else if (CalcTypeEnum.MUL.getCode().equals(calcType)) {
result = mul(num1, num2);
} else if (CalcTypeEnum.DIV.getCode().equals(calcType)) {
result = div(num1, num2);
}
System.out.println(result);
}
}
运行结果:
那么如果我们需要对计算器增强功能,假如我们再为计算器增加一个求余数的功能,该如何实现呢?
-
枚举类CalcTypeEnum增加乘方的枚举值
-
修改Calculator类,增加一个乘方的方法,然后在calc方法中再增加一个else if 块
这不太好,我们希望新增运算功能时只需要新增代码,不要修改原来已有的代码。
3.将具体运算分别封装成单独的类中(示例3)
为了达到新增运算方式不修改代码只增加代码的方式,我们需要将每个运算继续抽象,我们新增一个计算的接口,并将加减乘除求余数分别封装到每个具体的实现类里面。
新建CalcStrategy.java接口类:
/**
* @Description: 定义一个计算策略的接口.(实现啥功能的总接口)
* @Author: Zoutao
* @Date: 2020/4/10
*/
public interface CalcStrategy {
// 策略行为方法:对应功能的业务方法
int calc(int num1, int num2);
}
新建AddStrategy类,且实现CalcStrategy接口,
/**
* 加法操作
*/
public class AddStrategy implements CalcStrategy {
@Override
public int calc(int num1, int num2) {
System.out.println("当前进行加法操作~~~");
System.out.println("加法运算其它业务逻辑start");
System.out.println("此处省略几十行代码...");
System.out.println("加法运算其它业务逻辑end");
return num1 + num2;
}
}
新建SubStrategy类,且实现CalcStrategy接口,
/**
* 减法操作.
*/
public class SubStrategy implements CalcStrategy {
@Override
public int calc(int num1, int num2) {
System.out.println("当前进行减法操作~~~");
return num1 - num2;
}
}
新建MulStrategy类,且实现CalcStrategy接口,
/**
* 对每一类操作实现上面总策略接口
* 乘法操作
*/
public class MulStrategy implements CalcStrategy {
@Override
public int calc(int num1, int num2) {
System.out.println("当前进行乘法操作~~~");
return num1 * num2;
}
}
新建DivStrategy类,且实现CalcStrategy接口,
/**
* 除法操作
*/
public class DivStrategy implements CalcStrategy {
@Override
public int calc(int num1, int num2) {
return num1 / num2;
}
}
我们增加一个求余数的运算只需要增加一个枚举值并新增一个求余的实现类, 这样我们就实现了新增一个功能只需要新增代码而不用修改之前的代码的目的。
修改CalcTypeEnum.java枚举类,新增一个求余数功能。
/**
* @Description: 枚举类
* @Author: Zoutao
* @Date: 2020/4/10
*/
public enum CalcTypeEnum {
ADD(1,"加法操作"),
SUB(2,"减法操作"),
MUL(3,"乘法操作"),
DIV(4,"除法操作"),
// 新增-求余数功能
REM(5, "求余操作"),;
private Integer code;
private String description;
//构造方法:enum的构造方法只能被声明为private权限或不声明权限
CalcTypeEnum(Integer code, String description) {
this.code = code;
this.description = description;
}
public Integer getCode() {
return code;
}
public String getDescription() {
return description;
}
/**
* 自己定义一个静态方法,通过code返回枚举常量对象
* @param code
* @return
*/
public static CalcTypeEnum getValue(int code){
for (CalcTypeEnum calc : values()) {
if(calc.getCode()== code){
return calc;
}
}
return null;
}
}
新建RemStrategy.java实现类:
/**
* 求余操作
*/
public class RemStrategy implements CalcStrategy {
@Override
public int calc(int num1, int num2) {
System.out.println("当前进行求余操作~~~");
return num1 % num2;
}
}
新建测试类Main3.java:
/**
* @Description: 测试类
* @Author: Zoutao
* @Date: 2020/4/10
*/
public class Main3 {
// 用户要计算的类型
private static final int CALC_TYPE = 5;
public static void main(String[] args) {
// 根据用户要运算的类型调用相应实现类的方法
CalcStrategy calcStrategy = null;
if (CalcTypeEnum.ADD.getCode().equals(CALC_TYPE)) {
calcStrategy = new AddStrategy();
} else if (CalcTypeEnum.SUB.getCode().equals(CALC_TYPE)) {
calcStrategy = new SubStrategy();
} else if (CalcTypeEnum.MUL.getCode().equals(CALC_TYPE)) {
calcStrategy = new MulStrategy();
} else if (CalcTypeEnum.DIV.getCode().equals(CALC_TYPE)) {
calcStrategy = new DivStrategy();
} else if (CalcTypeEnum.REM.getCode().equals(CALC_TYPE)) {
calcStrategy = new RemStrategy();
}
// 直接调用CalcStrategy的calc接口方法
int result = calcStrategy.calc(10, 20);
System.out.println(result);
}
}
结果:
这样我们就实现了新增一个功能只需要新增代码而不用修改之前的代码的目的。
4. 为示例3引入上下文(示例4)
上下文只持有一个运算接口的引用并提供一个执行策略的方法,就是简单调用具体运算实现类的方法。
策略上下文,作用就是根据类型路由到具体的实现类。
新建一个CalcStrategyContext类:
/**
* @Description: 上下文类,持有一个运算接口的引用并提供一个执行策略的方法
* @Author: Zoutao
* @Date: 2020/4/10
*/
public class CalcStrategyContext {
// 运算接口
private CalcStrategy strategy;
// 通过构造函数或者set方法赋值
public CalcStrategyContext(CalcStrategy strategy) {
this.strategy = strategy;
}
// 简单调用具体实现对应的方法
public int executeStrategy(int a, int b) {
return strategy.calc(a, b);
}
public CalcStrategy getStrategy() {
return strategy;
}
public void setStrategy(CalcStrategy strategy) {
this.strategy = strategy;
}
}
测试类Main4.java:
/**
* @Description: 调用上下文类中的方法
* @Author: Zoutao
* @Date: 2020/4/10
*/
public class Main4 {
private static final Integer CALC_TYPE = 1;
public static void main(String[] args) {
CalcStrategy calcStrategy = null;
if (CalcTypeEnum.ADD.getCode().equals(CALC_TYPE)) {
//calcStrategy = new AddStrategy();
} else if (CalcTypeEnum.SUB.getCode().equals(CALC_TYPE)) {
calcStrategy = new SubStrategy();
} else if (CalcTypeEnum.MUL.getCode().equals(CALC_TYPE)) {
calcStrategy = new MulStrategy();
} else if (CalcTypeEnum.DIV.getCode().equals(CALC_TYPE)) {
calcStrategy = new DivStrategy();
}
// 这里不再直接调用接口方法,而是调用上下文类中的方法=调用CalcStrategyContext的executeStrategy方法
// 上下文就是对接口的一种简单的装饰和封装
CalcStrategyContext context = new CalcStrategyContext(calcStrategy);
int result = context.executeStrategy(20, 30);
System.out.println(result);
}
}
注意~此示例和上个示例不同的就是调用方法的对象不同,
- 一个是直接调用CalcStrategy的calc接口的方法,
- 一个是调用CalcStrategyContext的executeStrategy上下文中的方法。
对于去除原来的冗余if-else代码块,目前看起来示例3,4效果是一致的。
那么为什么要多此一步呢?引入上下文类的关键,在于接下来所介绍的策略模式。(示例4就是策略模式的具体实现。)
策略(Strategy)模式
策略(Strategy)模式:又名Policy,它的用意是定义一组算法,把它们一个个封装起来,并且使他们可以相互替换。策略模式可以独立于使用他们的客户端而变化。
策略设计模式的特点
- 需要提供一个策略接口类
- 需要提供多个策略接口的实现类
- 需要提供一个策略上下文类
上面的多个示例图示:
策略设计模式优点
- 可以*切换算法(具体实现类)
- 避免了多条件的判断(干掉了多重的if-else)
- 扩展性好(增加新功能时只需增加新代码而不需修改原代码)
5.通过策略设计模式干掉if-else(方式一)
采用策略设计模式 + 工厂模式,枚举的方式。
新建一个CalcTypeEnum2类:
/**
* @Description: 策略设计模式+工厂模式的综合体
* 通过配置枚举类。在枚举中增加对应的运算实现类,
* 并提供一个根据code来获取对应的枚举类的方法,
* 获取到枚举类了就获取到对应的实现类
* @Author: Zoutao
* @Date: 2020/4/10
*/
public enum CalcTypeEnum2 {
// code一般设置为具体实现类的id/标识符,new对象采用了工厂模式
ADD("Add", "加法操作", new AddStrategy()),
SUB("Sub", "减法操作", new SubStrategy()),
MUL("Mul", "乘法操作", new MulStrategy()),
DIV("Div", "除法操作", new DivStrategy()),;
private String code; //标识符id
private String description; // 描述
private CalcStrategy calcStrategy; // 计算策略接口的对象
CalcTypeEnum2(String code, String description, CalcStrategy calcStrategy) {
this.code = code;
this.description = description;
this.calcStrategy = calcStrategy;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
public CalcStrategy getCalcStrategy() {
return calcStrategy;
}
// 根据code获取对应的枚举类型
public static CalcTypeEnum2 getCalcTypeEnum(String code) {
for (CalcTypeEnum2 calcTypeEnum : CalcTypeEnum2.values()) {
if (calcTypeEnum.getCode().equals(code)) {
return calcTypeEnum;
}
}
System.out.println("对应计算策略不存在,[type={\"+type+\"}]");
return null;
}
/**
* 测试类
*/
private static final String CALC_TYPE = "Sub"; // code=Add/Sub/Mul/Div 代表
public static void main(String[] args) {
// 消除if else,根据code获取到对应的枚举类,进而获取到对应的计算实现类
CalcStrategy addStrategy = CalcTypeEnum2.getCalcTypeEnum(CALC_TYPE).getCalcStrategy();
CalcStrategyContext context = new CalcStrategyContext(addStrategy);
int result = context.executeStrategy(20, 30);
System.out.println(CalcTypeEnum2.getCalcTypeEnum(CALC_TYPE)); // new了个CALC_TYPE对应的对象
System.out.println(context.toString()); // 生成了对应的上下文对象
System.out.println(result); // 上下文对象调用具体实现方法
}
}
其中还需要CalcStrategyContext类,示例中已经写好了,这里就不重复了。
结果:
6.通过反射彻底干掉if else(方式二)
我们将每个具体的实现类变成单例模式,这里通过懒汉模式来实现单例类。
新建一个AddStrategy2类,实现CalcStrategy接口:
/**
* @Description: 通过反射,将每个具体的实现类变成单例模式
* @Author: Zoutao
* @Date: 2020/4/10
*/
/**
* 加法操作
*/
public class AddStrategy implements CalcStrategy {
private static AddStrategy addStrategy;
private AddStrategy() {
}
// 懒汉模式实现单例类
public static AddStrategy getInstance() {
if (addStrategy == null) {
addStrategy = new AddStrategy();
}
return addStrategy;
}
@Override
public int calc(int num1, int num2) {
System.out.println("加法运算其它业务逻辑start");
System.out.println("此处省略几十行代码...");
System.out.println("加法运算其它业务逻辑end");
return num1 + num2;
}
}
通过反射获取某个具体的类,然后调用具体类的getInstance方法,从而获取对应的运算实现类。
新建CalcStrategyUtils类:
/**
* @Description: 反射获取某个具体的类
* @Author: Zoutao
* @Date: 2020/4/10
*/
public class CalcStrategyUtils {
public static CalcStrategy getCalcStrategy(String calcType) {
try {
// 这里包名是写死的,默认的src目录下,开发时需将实现类统一放到自定义的某包下,类的命名也是有规则的,以Strategy作为后缀。-反射原理
String path = calcType.concat("Strategy");
Class<?> clazz = Class.forName(path);
CalcStrategy instance = (CalcStrategy) clazz.getDeclaredMethod("getInstance").invoke(null,null);
return instance;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Load [" + calcType.concat("Strategy") + "] Error :", e);
}
}
/**
* 测试类
*/
private static final String CALC_TYPE = "Add";
public static void main(String[] args) {
// 通过反射获取到具体实现类型,从而消除if else
CalcStrategy addStrategy = CalcStrategyUtils.getCalcStrategy(CALC_TYPE);
System.out.println(addStrategy);
CalcStrategyContext context = new CalcStrategyContext(addStrategy);
int result = context.executeStrategy(20, 30);
System.out.println(result);
}
}
运行结果:
策略模式在程序设计中很常用,有篇文章叫 “你还在用if-else吗?” 讲的很好。
策略模式不但继承的代替方案而且能很好地解决if-else问题。
下一篇文章会举个实战例来说明,怎么使用策略模式。
参考地址:
https://www.toutiao.com/i6813584351410782723/
https://www.iteye.com/blog/alaric-1920714
上一篇: 一篇文章彻底搞懂Java虚拟机垃圾回收(GC)机制
下一篇: Java学习笔记:基础篇(1)