Как заменить много if операторов в Java

Как заменить много if операторов в Java

**

1. обзор

Решающие конструкции являются важной частью любого языка программирования. Но мы попадаем в кодирование огромного количества вложенных операторов if, которые делают наш код более сложным и сложным в обслуживании.

В этом руководстве мы рассмотримvarious ways of replacing nested if statements.

Давайте рассмотрим различные варианты упрощения кода.

2. Тематическое исследование

Часто мы сталкиваемся с бизнес-логикой, которая включает в себя множество условий, и каждое из них требует различной обработки. В качестве демонстрации возьмем пример класса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:

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.

В нашем примере давайте определим интерфейсOperation, который имеет единственный методapply:

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

В этом примере мы увидели, как ответственность делегируется слабо связанным объектам, обслуживаемым фабричным классом. Но возможны случаи, когда вложенные операторы просто перемещаются в фабричный класс, что противоречит нашей цели.

В качестве альтернативы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;
    }
}

Наконец, давайте представим новый метод вCalculator, который принимает и выполняетCommand:

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

Давайте рассмотрим пример, разработав простойRuleEngine, который обрабатываетExpression через наборRules и возвращает результат из выбранногоRule. Сначала мы определим интерфейс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();
    }
}

RuleEngine принимает объектExpression и возвращаетResult. Теперь, давайте спроектируем классExpression как группу из двух объектовInteger с применениемOperator:

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

И, наконец, давайте определим собственный классAddRule, который оценивается, только когда указанADD Operation:

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

Теперь вызовемRuleEngine сExpression:

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

**