Javaでif文を置き換える方法
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レポジトリ]で完全なソースコードを見つけることができます。
-
**