Java中多個ifelse語句的替代設計

今天在改老代碼的過程中,親眼見證了一段30個if-else嵌套的代碼...

然後蒐集了一些資料做了以下簡單整理。

概述

ifelse是任何編程語言的重要組成部分。但是我們編寫了大量嵌套的if語句,這使得我們的代碼更加複雜和難以維護。

接下來,讓我們探索如何簡化代碼的中的ifelse語句寫法。

案例研究

我們經常遇到涉及很多條件的業務邏輯,並且每個邏輯都需要不同的處理方式。以Calculator類爲例。我們將有一個方法,它接受兩個數字和一個運算符作爲輸入,並根據操作返回結果:

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

我們也可以使用switch語句來實現它:

    switch (operator) {
    case "add":
        result = a + b;
        break;
    // other cases
    }
    return result;
}

在典型的開發中,if語句可能會變得更大,更復雜。此外,當存在複雜條件時,switch語句不適合。

擁有嵌套決策結構的另一個副作用是它們變得難以管理。例如,如果我們需要添加一個新的運算符,我們必須添加一個新的if語句並實現該操作。

重構

可以通過設計模式,來達到我們要的效果。

工廠模式

很多時候,我們遇到ifelse結構,最終在每個分支中執行類似的操作。這提供了提取工廠方法的機會,該工廠方法返回給定類型的對象並基於具體對象行爲執行操作。

對於我們的示例,讓我們定義一個具有單個apply方法的Operation接口:

    int apply(int a, int b);
}

該方法將兩個數字作爲輸入並返回結果。讓我們定義一個用於執行添加的類:

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

我們現在將實現一個工廠類,它根據給定的運算符返回Operation的實例:

public class OperatorFactory {
    static Map<String, Operation> operationMap = new HashMap<>();
    static {
        operationMap.put("add", new Addition());
        operationMap.put("divide", new Division());
        // more operators
    }
 
    public static Optional<Operation> getOperation(String operator) {
        return Optional.ofNullable(operationMap.get(operator));
    }
}

現在,在Calculator類中,我們可以查詢工廠以獲取相關操作並應用源數:

public int calculateUsingFactory(int a, int b, String operator) {
    Operation targetOperation = OperatorFactory
      .getOperation(operator)
      .orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
    return targetOperation.apply(a, b);
}

在這個例子中,我們已經看到了如何將責任委託給工廠類提供的鬆散耦合對象。但是有可能嵌套的if語句只是轉移到了工廠類,這違背了我們的目的。

或者,我們可以在Map中維護一個對象存儲庫,可以查詢該存儲庫以進行快速查找。正如我們所見,OperatorFactory#operationMap服務於我們的目的。我們還可以在運行時初始化Map並將它們配置爲查找。

使用枚舉

除了使用Map之外,我們還可以使用Enum來標記特定的業務邏輯。之後,我們可以在嵌套的if語句或switch case 語句中使用它們。或者,我們也可以將它們用作對象的工廠並制定策略以執行相關的業務邏輯。

這樣可以減少嵌套if語句的數量,並將責任委託給單個Enum值。

讓我們看看我們如何實現它。首先,我們需要定義我們的枚舉:


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

可以觀察到,這些值是不同運算符的標籤,將進一步用於計算。我們總是可以選擇在嵌套的if語句或switch case中使用這些值作爲不同的條件,但讓我們設計一種將邏輯委託給Enum本身的替代方法。

我們將爲每個Enum值定義方法並進行計算。例如:

    @Override
    public int apply(int a, int b) {
        return a + b;
    }
},
// other operators
 
public abstract int apply(int a, int b);

然後在Calculator類中,我們可以定義一個執行操作的方法:

public int calculate(int a, int b, Operator operator)

{

return operator.apply(a, b);
}

現在,我們可以通過使用Operator#valueOf()方法將String值轉換爲Operator來調用該方法:

@Test
public void test() {
    Calculator calculator = new Calculator();
    int result = calculator.calculate(3, 4, Operator.valueOf("ADD"));
    assertEquals(7, result);
}

命令模式

在前面的討論中,我們已經看到使用工廠類來返回給定運算符的正確業務對象的實例。稍後,業務對象用於在計算器中執行計算。

我們還可以設計一個Calculator#calculate方法來接受可以在輸入上執行的命令。這將是替換嵌套if語句的另一種方法。

我們首先定義我們的Command接口:


public interface Command {
    Integer execute();
}

接下來,讓我們實現一個AddCommand:


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

最後,讓我們在Calculator中引入一個接受並執行Command的新方法:


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

接下來,我們可以通過實例化AddCommand調用計算並將其發送到Calculator#calculate方法:


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

規則引擎

當我們最終編寫大量嵌套if語句時,每個條件都描述了一個業務規則,必須對其進行評估才能處理正確的邏輯。規則引擎從主代碼中獲取了這種複雜性。一個RuleEngine評估規則和返回基於輸入的結果。

讓我們通過設計一個簡單的RuleEngine來演示一個例子,該RuleEngine通過一組規則處理Expression並返回所選規則的結果。首先,我們將定義一個Rule接口:


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

其次,讓我們實現一個RuleEngine:


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();
    }
}

所述RuleEngine接受一個表達對象,並返回結果。現在,讓我們將Expression類設計爲一組包含兩個Integer對象的Operator,它將被應用:


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

最後讓我們定義一個自定義的AddRule類,該類僅在指定ADD操作時進行求值:


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 test() {
    Expression expression = new Expression(5, 5, Operator.ADD);
    RuleEngine engine = new RuleEngine();
    Result result = engine.process(expression);
 
    assertNotNull(result);
    assertEquals(10, result.getValue());
}

結論
通過這些設計模式,可以作爲我們的ifelse語句的替代方案,具體用哪一種可以根據你的實際業務場景來決定。

5萬人關注的大數據成神之路,不來了解一下嗎?
5萬人關注的大數據成神之路,真的不來了解一下嗎?
5萬人關注的大數據成神之路,確定真的不來了解一下嗎?

歡迎您關注《大數據成神之路》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章