在Java中替换掉繁杂的if语句

if else每种编程语言都不可或缺的条件语句,在编程时会大量的用到。一般建议嵌套不要超过三层,如果一段代码存在过多的嵌套,代码的可读性就会急速下降,后期维护难度也大大提高。

一、简述

通常会遇到一些需要做一系列条件处理的业务逻辑,并且它们每一个都需要不同的处理。来看一下 Calulator(计算器)类的一个例子。下面是带有两个数字类型参数,一个操作符参数以及基于操作的数值返回值的一个方法:

public int calculate(int a, int b, String operator) {
    int result = Integer.MIN_VALUE;
    if ("add".equals(operator)) {
        result = a + b;
    } else if ("subtract".equals(operator)) {
        result = a - b;
    } else if ("multiply".equals(operator)) {
        result = a * b;
    } else if ("divide".equals(operator)) {
        result = a / b;
    }
    return result;
}

通常也能使用 switch 语句来操作:

public int calculate(int a, int b, String operator) {
    int result = Integer.MIN_VALUE;
    switch (operator) {
        case "add":
            result = a + b;
        case "multiply":
            result = a * b;
        case "divide":
            result = a / b;
        case "subtract":
            result = a - b;
            break;
    }
    return result;
}

switch 语句其实并没有任何实质性的优化。嵌套条件声明编程使得程序变得难以管理。如果需要新添加一个操作,相应需要添加一个新的 if 条件以及条件的实现,甚至需要回归整个业务代码。

二、工厂类

经常遇见许多条件声明,它们是用来处理每个分支中相似的操作。可以提取一个返回具体类型的对象并且根据具体对象行为执行操作的工厂类。例如,定义一个带有单独的 apply 方法的操作接口:

public interface CalOperation {
    int apply(int a, int b);
}

单独定义业务类:
加:

public class Addition implements CalOperation {
    @Override
    public int apply(int a, int b) {
        return a + b;
    }
}

减:

public class Subtraction implements CalOperation {
    @Override
    public int apply(int a, int b) {
        return a - b;
    }
}

乘:

public class Multiplication implements CalOperation {
    @Override
    public int apply(int a, int b) {
        return a * b;
    }
}

除:

public class Division implements CalOperation {
    @Override
    public int apply(int a, int b) {
        return a / b;
    }
}

现在实现一个基于给定操作符返回相应结果的工程类:

public class OperatorFactory {
    static Map<String, CalOperation > operationMap = new HashMap<>();
    //在静态块中先把初始化工作全部做完  
    static {
        operationMap.put("add", new Addition());
        operationMap.put("subtract", new Subtraction());
        operationMap.put("multiply", new Multiplication());
        operationMap.put("divide", new Division());
    }
    public static CalOperation getOperation(String operator) {
        return operationMap.get(operator);
    }
}

在 Calculator 类中,能够通过查询工厂来获取相关的操作并且应用于其中:

public class Calculator {
   public String cal( String operator) {
   // 一行代码搞定!之前的 if/else也没了!
   return OperatorFactory.getOperation(operator).apply(); 
   }
}

该例通过工厂类来将逻辑业务责任分发委托给一系列轻耦合对象。但是如果只是简单地将嵌套 if 语句转移成工厂类,这明显不符合预期。作为另外的选择,通过维护能够被快速查询的对象仓库map(映射),来达成目的。也可以在运行时定义映射对象并且配置它们用于查找。这样的话以后想扩展条件也很容易,只需要增加新代码,而不需要动以前的业务代码,非常符合“开闭原则”。

三、命令模式(command pattern)

使用工厂类可以返回指定操作符的对应的业务对象实例,该实例用于 Claculator 类中执行计算操作。也可以设计一个 Calculator.calculate 方法来接收一个可以执行输入的指令。这是另外一种来代替嵌套 if 语句的方法。定义一个 Command 接口:

public interface Command {
    Integer execute();
}

其中一个实现类 AddCommand:

public class AddCommand implements Command {
    public AddCommand(int a, int b) {
        this.a = a;
        this.b = b;
    }
    @Override
    public Integer execute() {
        return a + b;
    }
}

最后,在 Calculator 类中定义一个用于接收操作和执行操作的新方法:

public class Calculator {
    public int calculate(Command command) {
        return command.execute();
    }
}

通过实例化 AddCommand 对象并且将它作为参数传递到 Calculator.calculate 方法当中用以调用计算方法:

@Test
public void whenCalculateUsingCommand_thenReturnCorrectResult() {
    Calculator calculator = new Calculator();
    int result = calculator.calculate(new AddCommand(3, 7));
    assertEquals(10, result);
}

三、策略模式

策略模式和工厂模式写起来其实区别也不大!

在上面工厂模式代码的基础上,按照策略模式的指导思想,来创建一个所谓的策略上下文类,这里命名为 CalContext:

public class CalContext {
    private CalOperation calOperation;
    public CalContext(CalOperation calOperation) {
        this.calOperation = calOperation;
    }
    public int execute(int a, int b) {
        return calOperation.apply(a, b);
    }
}

很明显上面传入的参数 calOperation 就是表示不同的“策略”。在业务代码里传入不同的角色,即可得到不同的操作结果:

public class Calculator {
    public int apply(int a, int b, CalOperation calOperation) {
        CalContext calContext = new CalContext(calOperation);
        return calContext.execute(a, b);
    }
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        int add = calculator.apply(6, 2, new Addition());
        System.out.println(add);
        int sub = calculator.apply(6, 2, new Subtraction());
        System.out.println(sub);
        int mul = calculator.apply(6, 2, new Multiplication());
        System.out.println(mul);
        int div = calculator.apply(6, 2, new Division());
        System.out.println(div);
    }
}

四、使用枚举

除了映射对象(map)的使用之外,也可以使用枚举来标记特定的逻辑业务。在这之后,能通过它来代替嵌套 if 语句或者 swtich 语句。作为其它处理,也可以使用它们作为对象工厂并且整理用于处理相关的业务逻辑操作。这会减少嵌套 if 语句的数量并且将业务责任委托给独立的枚举变量中。定义一个枚举类:

public enum CalEnum{
    ADD, SUBTRACT, MULTIPLY, DIVIDE
}

这些值是不同操作符的标签,并且会运用到之后的计算当中。就像嵌套 if 语句和 switch 语句那样,可以将这些值当作选项来使用。然而,可以设计一种能够将逻辑委托给枚举本身的替代方法,为每一个枚举量都定义了各自的方法并且进行了计算操作。例如:

CalOperation 类:

public interface CalOperation {
    int apply(int a, int b);
}

CalEnum 类:

public enum CalEnum implements CalOperation {
    //加
    ADD {
        @Override
        public int apply(int a, int b) {
            return a + b;
        }
    },
    //减
    MULTIPLY {
        @Override
        public int apply(int a, int b) {
            return a - b;
        }
    },
    //乘
    SUBTRACT {
        @Override
        public int apply(int a, int b) {
            return a * b;
        }
    },
    //除
    DIVIDE {
        @Override
        public int apply(int a, int b) {
            return a / b;
        }
    }
}

调用如下:

public class Calculator {
    public int apply(int a, int b, String calOperation) {
        // 一行代码搞定!之前的if/else没了!
        return CalEnum.valueOf(calOperation).apply(a, b);
    }
    public static void main(String[] args) {
        CalClass cc = new CalClass();
        int add = cc.apply(1, 2, "MULTIPLY");
        System.out.println(add);
    }
}

五、规则引擎(rule engine)

当编写了大量的嵌套 if 语句时,每一个条件都描述了特定的业务规则,用于评估正确逻辑操作的执行。规则引擎将这些复杂的判断从主代码中去掉。规则引擎是用于评估规则并且基于输入返回结果。这个规则引擎是通过一组规则来处理表达式,并从选中的规则返回结果。定义一个规则接口:

public interface Rule {
    boolean evaluate(Expression expression);
    Result getResult();
}

接着,来实现一个规则引擎:

public class RuleEngine {
    private static List<Rule> rules = new ArrayList<>();
    static {
        rules.add(new AddRule());
    }
    public Result process(Expression expression) {
        Rule rule = rules.stream()
                         .filter(r -> r.evaluate(expression))
                         .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Expression does not matches any Rule"));
        return rule.getResult();
    }
}

这个规则引擎接收一个表达式并且返回 Result。现在,设计一个带有两个数值变量以及一个用于操作的 Operator 对象的表达式类:

public class Expression {
  private Integer x;
  private Integer y;
  private Operator operator; 
}

最后,定义一个 AddRule 类,它只在加操作中使用到:

public class AddRule implements Rule {
  @Override
  public boolean evaluate(Expression expression) {
  boolean evalResult = false;
  if (expression.getOperator() == Operator.ADD) {
  this.result = expression.getX() + expression.getY();
  evalResult = true;
}
  return evalResult;
  } 
}

现在,可以使用 Expression 来调用 RuleEngine 了:

@Test
public void whenNumbersGivenToRuleEngine_thenReturnCorrectResult() {
 Expression expression = new Expression(5, 5, Operator.ADD);
 RuleEngine engine = new RuleEngine();
 Result result = engine.process(expression);
 
 assertNotNull(result);
 assertEquals(10, result.getValue());
}

六、提前 return

这是判断条件取反的做法,代码在逻辑表达上会更清晰,看下面代码:

if(condition){
  //do something
}else{
  return xxx;
}

其实,完全可以先判断 !condition,干掉else。

if(!condition){
  return xxx;
}
  //do something

七、Optional主要用于非空判断,jdk8新特性

使用之前:

if(user == null){
  //do action1
}else{
  //do action2
}

如果登录用户为空,执行 action1,否则执行 action 2。使用 Optional 优化之后,让非空校验更加优雅,间接的减少 if 操作:

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1) .orElse(action2);

八、数组小技巧

来自 google 解释,这是一种编程模式,叫做表驱动法,本质是从表里查询信息来代替逻辑语句,比如有这么一个场景,通过月份来获取当月的天数,仅作为案例演示,数据并不严谨。一般的实现:

public int getDays(int month) {
       if (month == 1) {
           return 31;
       } else if (month == 2) {
           return 29;
       } else if (month == 3) {
           return 31;
       } else if (month == 4) {
           return 30;
       } else if (month == 5) {
           return 31;
       } else if (month == 6) {
           return 30;
       } else if (month == 7) {
           return 31;
       } else if (month == 8) {
           return 31;
       } else if (month == 9) {
           return 30;
       } else if (month == 10) {
           return 31;
       } else if (month == 11) {
           return 30;
       } else if (month == 12) {
           return 31;
       }
       return 0;
}

优化后的代码:

int[] monthDays = {
        31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
public int getDays(int month) {
    return monthDays[--month];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容