So ersetzen Sie viele if-Anweisungen in Java

So ersetzen Sie viele if-Anweisungen in Java

**

1. Überblick

Entscheidungskonstrukte sind ein wesentlicher Bestandteil jeder Programmiersprache. Wir landen jedoch in der Codierung einer großen Anzahl verschachtelter if-Anweisungen, die unseren Code komplexer und schwieriger zu warten machen.

In diesem Tutorial gehen wir durch dievarious ways of replacing nested if statements.

Lassen Sie uns verschiedene Optionen erkunden, wie wir den Code vereinfachen können.

2. Fallstudie

Oft stoßen wir auf eine Geschäftslogik, die viele Bedingungen mit sich bringt und für die jeweils eine andere Verarbeitung erforderlich ist. Nehmen wir für eine Demo das Beispiel einerCalculator-Klasse. Wir werden eine Methode haben, die zwei Zahlen und einen Operator als Eingabe verwendet und das Ergebnis basierend auf der Operation zurückgibt:

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

Wir können dies auch mitswitch Anweisungen: implementieren

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

In der typischen Entwicklung sindthe if statements may grow much bigger and more complex in nature. Auchthe switch statements do not fit well when there are complex conditions.

Ein weiterer Nebeneffekt von verschachtelten Entscheidungskonstrukten ist, dass sie nicht mehr verwaltet werden können. Wenn wir zum Beispiel einen neuen Operator hinzufügen müssen, müssen wir eine neue if-Anweisung hinzufügen und die Operation implementieren.

3. Refactoring

Lassen Sie uns die alternativen Optionen erkunden, um die oben genannten komplexen if-Anweisungen durch viel einfacheren und verwaltbaren Code zu ersetzen.

3.1. Fabrikklasse

Oft stoßen wir auf Entscheidungskonstrukte, die in jedem Zweig die gleiche Operation ausführen. Dies bietetextract a factory method which returns an object of a given type and performs the operation based on the concrete object behavior die Möglichkeit.

In unserem Beispiel definieren wir eineOperation-Schnittstelle mit einer einzelnenapply-Methode:

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

Die Methode nimmt zwei Zahlen als Eingabe und gibt das Ergebnis zurück. Definieren wir eine Klasse für die Durchführung von Ergänzungen:

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

Wir implementieren jetzt eine Factory-Klasse, die Instanzen vonOperation basierend auf dem angegebenen Operator zurückgibt:

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

Jetzt können wir in der KlasseCalculator die Factory abfragen, um die relevante Operation abzurufen und auf die Quellennummern anzuwenden:

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

In diesem Beispiel haben wir gesehen, wie die Verantwortung an lose gekoppelte Objekte delegiert wird, die von einer Factory-Klasse bedient werden. Es könnte jedoch Chancen geben, dass verschachtelte if-Anweisungen einfach in die Factory-Klasse verschoben werden, die unseren Zweck missachtet.

Alternativwe can maintain a repository of objects in a Map which could be queried for a quick lookup. Wie wir gesehen haben, erfülltOperatorFactory#operationMap unseren Zweck. Wir könnenMap auch zur Laufzeit initialisieren und für die Suche konfigurieren.

3.2. Verwendung von Enums

Zusätzlich zur Verwendung vonMap,we can also use Enum to label particular business logic. Danach können wir sie entweder in den verschachteltenif statements oderswitch casestatements verwenden. Alternativ können wir sie auch als Objektfabrik verwenden und sie strategisch so ausrichten, dass sie die entsprechende Geschäftslogik ausführen.

Dies würde auch die Anzahl der verschachtelten if-Anweisungen verringern und die Verantwortung an einzelneEnum-Werte delegieren.

Mal sehen, wie wir es erreichen können. Zuerst müssen wir unsereEnum definieren:

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

Wie wir sehen können, sind die Werte die Bezeichnungen der verschiedenen Operatoren, die für die Berechnung weiter verwendet werden. Wir haben immer die Möglichkeit, die Werte als unterschiedliche Bedingungen in verschachtelten if-Anweisungen oder Switch-Fällen zu verwenden. Lassen Sie uns jedoch eine alternative Methode zum Delegieren der Logik anEnumelbst entwerfen.

Wir definieren Methoden für jeden derEnum-Werte und führen die Berechnung durch. Zum Beispiel:

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

public abstract int apply(int a, int b);

Und dann können wir in der KlasseCalculatoreine Methode definieren, um die Operation auszuführen:

public int calculate(int a, int b, Operator operator) {
    return operator.apply(a, b);
}

Jetzt können wir die Methode mitconverting the String value to the Operator by using the Operator#valueOf() method aufrufen:

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

3.3. Befehlsmuster

In der vorherigen Diskussion haben wir die Verwendung der Factory-Klasse gesehen, um die Instanz des richtigen Geschäftsobjekts für den angegebenen Operator zurückzugeben. Später wird das Geschäftsobjekt verwendet, um die Berechnung inCalculator durchzuführen.

We can also design a Calculator#calculate method to accept a command which can be executed on the inputs. Dies ist eine weitere Möglichkeit, verschachtelteif statements zu ersetzen.

Wir definieren zuerst dieCommand-Schnittstelle:

public interface Command {
    Integer execute();
}

Als nächstes implementieren wir einAddCommand:

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

Lassen Sie uns abschließend eine neue Methode inCalculator einführen, dieCommand akzeptiert und ausführt:

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

Als nächstes können wir die Berechnung aufrufen, indem wirAddCommand instanziieren und an die MethodeCalculator#calculate senden:

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

3.4. Regel-Engine

Wenn wir am Ende eine große Anzahl verschachtelter if-Anweisungen schreiben, stellt jede der Bedingungen eine Geschäftsregel dar, die ausgewertet werden muss, damit die richtige Logik verarbeitet werden kann. Eine Regel-Engine nimmt dem Hauptcode eine solche Komplexität. RuleEngine evaluates the Rules and returns the result based on the input.

Lassen Sie uns ein Beispiel durchgehen, indem wir ein einfachesRuleEngine entwerfen, das einExpression durch eine Menge vonRules verarbeitet und das Ergebnis aus den ausgewähltenRule zurückgibt. Zunächst definieren wir dieRule-Schnittstelle:

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

Zweitens implementieren wir einRuleEngine:

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

DasRuleEngine akzeptiert einExpression-Objekt und gibt dasResult zurück. Nun wollen wir, die KlasseExpressionals Gruppe von zweiInteger Objekten mit denOperator entwerfen, die angewendet werden:

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

Und schließlich definieren wir eine benutzerdefinierteAddRule-Klasse, die nur ausgewertet wird, wennADD Operation angegeben wird:

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

Wir rufen jetztRuleEngine mitExpression auf:

@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. Fazit

In diesem Lernprogramm haben wir verschiedene Optionen untersucht, um komplexen Code zu vereinfachen. Wir haben auch gelernt, geschachtelte if-Anweisungen durch effektive Entwurfsmuster zu ersetzen.

Wie immer können wir den vollständigen Quellcode überGitHub repository finden.

**