1概要

決定構造は、あらゆるプログラミング言語の重要な部分です。しかし、コードをより複雑にし、保守するのを難しくする、膨大な数のネストしたifステートメントをコーディングすることになりました。

このチュートリアルでは、入れ子になったif文を置き換えるためのさまざまな方法について説明します。

コードを単純化する方法について、さまざまな選択肢を検討しましょう。


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

典型的な開発では、

if文は本質的にもっと大きく複雑になる可能性があります

。また、

switchステートメントは複雑な条件があるときにはうまく合いません

ネストした意思決定構文を持つことによるもう1つの副作用は、それらが管理できなくなることです。たとえば、新しい演算子を追加する必要がある場合は、新しいif文を追加して操作を実装する必要があります。


3リファクタリング

上記の複雑なif文を、より単純で管理しやすいコードに置き換えるための代替オプションを検討しましょう。


3.1. ファクトリークラス

多くの場合、各ブランチで同様の操作を実行することになる意思決定構造に遭遇します。これは、与えられた型のオブジェクトを返し、具体的なオブジェクトの振舞いに基づいて操作を実行するファクトリメソッドを抽出する機会を提供します。

例として、単一の

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<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

を初期化し、それらをルックアップ用に設定することもできます。


3.2. 列挙型の使用


Mapの使用に加えて、** 特定のビジネスロジックにラベルを付けるために

Enum

を使用することもできます。その後、ネストされた

ifステートメント

または

スイッチケース__ステートメントのいずれかでそれらを使用できます。あるいは、それらをオブジェクトのファクトリーとして使用し、それらを関連ビジネスロジックを実行するように戦略化することもできます。

それは同様に入れ子になったifステートメントの数を減らし、責任を個々の

Enum

値に委譲します。

それを達成する方法を見てみましょう。最初に、

Enum

を定義する必要があります。

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

ご覧のとおり、値はさまざまな演算子のラベルであり、これらは計算に使用されます。入れ子になったifステートメントまたはswitchケースでは、値をさまざまな条件として使用するオプションが常にありますが、ロジックを

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

これで、

Operator#valueOf()

メソッドを使用して

String

値を

Operator

に変換することで、メソッドを呼び出すことができます。

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


3.3. コマンドパターン

これまでの説明では、与えられた演算子に対して正しいビジネス・オブジェクトのインスタンスを戻すためのファクトリー・クラスの使用について説明しました。その後、ビジネスオブジェクトは

Calculator

で計算を実行するために使用されます。

  • 入力に対して実行できるコマンドを受け付けるように、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;
    }
}

最後に、

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



Rules

を評価し、入力に基づいて結果を返します。

単純な

RuleEngine

を設計することによって例を見てみましょう。それは

Rules

のセットを通して

Expression

を処理し、選択された

Rule

から結果を返します。まず、

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

オブジェクトを受け取り、

Result

を返します。

__、


では、

Expression

クラスを、適用される

Operator

を持つ2つの

Integer__オブジェクトのグループとして設計しましょう。

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ステートメントを置き換える方法も学びました。

いつものように、私達はhttps://github.com/eugenp/tutorials/tree/master/core-java-8[GitHubレポジトリ]で完全なソースコードを見つけることができます。

  • **