Javaでif文を置き換える方法

Javaで多くのifステートメントを置き換える方法

**

1. 概要

決定構造は、プログラミング言語の重要な部分です。 しかし、私たちはコードをより複雑にし、維持するのを難しくする、多数のネストされたifステートメントをコーディングすることになります。

このチュートリアルでは、various ways of replacing nested if statementsについて説明します。

コードを単純化する方法について、さまざまなオプションを見ていきましょう。

2. ケーススタディ

多くの場合、多くの条件を含むビジネスロジックに遭遇し、それぞれが異なる処理を必要とします。 デモのために、Calculatorクラスの例を見てみましょう。 入力として2つの数字と演算子を取り、操作に基づいて結果を返すメソッドがあります。

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ステートメント:を使用してこれを実装することもできます

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

典型的な開発では、the if statements may grow much bigger and more complex in nature。 また、the switch statements do not fit well when there are complex conditions

ネストされた決定構造を持つ別の副作用は、それらが管理不能になることです。 たとえば、新しい演算子を追加する必要がある場合は、新しいifステートメントを追加して、操作を実装する必要があります。

3. リファクタリング

上記の複雑なifステートメントをはるかに単純で管理しやすいコードに置き換えるための代替オプションを調べてみましょう。

3.1. 工場クラス

多くの場合、各分岐で同様の操作を実行する決定構造に遭遇します。 これはextract a factory method which returns an object of a given type and performs the operation based on the concrete object behaviorに機会を提供します。

この例では、単一のapplyメソッドを持つOperationインターフェースを定義しましょう。

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

このメソッドは2つの数値を入力として受け取り、結果を返します。 追加を実行するためのクラスを定義しましょう。

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

次に、指定された演算子に基づいてOperationのインスタンスを返すファクトリクラスを実装します。

public class OperatorFactory {
    static Map operationMap = new HashMap<>();
    static {
        operationMap.put("add", new Addition());
        operationMap.put("divide", new Division());
        // more operators
    }

    public static Optional 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ステートメントが単に目的を無効にするファクトリクラスにシフトされる可能性があります。

または、we can maintain a repository of objects in a Map which could be queried for a quick lookup。 これまで見てきたように、OperatorFactory#operationMapは私たちの目的を果たします。 実行時にMapを初期化し、ルックアップ用に構成することもできます。

3.2. 列挙型の使用

Map,we can also use Enum to label particular business logicの使用に加えて。 その後、ネストされたif statementsまたはswitch casestatementsのいずれかでそれらを使用できます。 または、それらをオブジェクトのファクトリとして使用し、戦略を立てて関連するビジネスロジックを実行することもできます。

これにより、ネストされたifステートメントの数も減り、個々のEnumの値に責任が委任されます。

それを達成する方法を見てみましょう。 最初に、Enumを定義する必要があります。

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

観察できるように、値はさまざまな演算子のラベルであり、さらに計算に使用されます。 ネストされたifステートメントまたはスイッチケースで値を異なる条件として使用するオプションは常にありますが、ロジックをEnum自体に委任する別の方法を設計しましょう。

Enum値ごとにメソッドを定義し、計算を行います。 例えば:

ADD {
    @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);
}

これで、converting the String value to the Operator by using the Operator#valueOf() methodによってメソッドを呼び出すことができます。

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

3.3. コマンドパターン

前の説明では、ファクトリクラスを使用して、指定された演算子の正しいビジネスオブジェクトのインスタンスを返すことを見てきました。 後で、ビジネスオブジェクトを使用してCalculatorで計算を実行します。

We can also design a Calculator#calculate method to accept a command which can be executed on the inputs。 これは、ネストされたif statementsを置き換える別の方法になります。

まず、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;
    }
}

最後に、Commandを受け入れて実行する新しいメソッドを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);
}

3.4. ルールエンジン

多数のネストされたifステートメントを書くことになったとき、各条件は、処理される正しいロジックのために評価されなければならないビジネスルールを表します。 ルールエンジンは、メインコードからこのような複雑さを取り除きます。 RuleEngine evaluates the Rules and returns the result based on the input.

Rulesのセットを介してExpressionを処理し、選択したRuleから結果を返す単純なRuleEngineを設計することにより、例を見ていきましょう。 まず、Ruleインターフェースを定義します。

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

次に、RuleEngineを実装しましょう。

public class RuleEngine {
    private static List 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();
    }
}

RuleEngineExpressionオブジェクトを受け入れ、Resultを返します。 ここで、,は、適用されるOperatorを使用して2つのIntegerオブジェクトのグループとしてExpressionクラスを設計しましょう。

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

最後に、ADD Operationが指定されている場合にのみ評価されるカスタム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());
}

4. 結論

このチュートリアルでは、複雑なコードを簡素化するためのさまざまなオプションを検討しました。 また、効果的な設計パターンを使用して、ネストされたifステートメントを置き換える方法も学びました。

いつものように、GitHub repositoryで完全なソースコードを見つけることができます。

**