Как кодировать эти условные операторы более элегантным и масштабируемым способом - PullRequest
0 голосов
/ 16 февраля 2019

В моем программном обеспечении мне нужно выбрать версию функции на основе 2 параметров.Например.

Render version 1 -> if (param1 && param2) == true;
Render version 2 -> if (!param1 && !param2) == true;
Render version 3 -> if only param1 == true;
Render version 4 -> if only param2 == true;

Итак, чтобы удовлетворить это требование, я написал код, который выглядит следующим образом -

if(param1 && param2) //both are true {
    version = 1;
}
else if(!param1 && !param2) //both are false {
    version = 2;
}
else if(!param2) //Means param1 is true {
    version = 3;
}
else { //Means param2 is true
    version = 4;
}

Определенно существует несколько способов кодирования этого, но я завершил этот подходпосле опробования различных подходов, потому что это самый читаемый код, который я мог придумать.
Но этот кусок кода определенно не масштабируется, потому что -

  1. Допустим, завтра мы хотим ввести новый параметр под названием param3.Тогда нет.чеков будет увеличиваться из-за нескольких возможных комбинаций.
  2. Для этого программного обеспечения я почти уверен, что в будущем нам придется учитывать новые параметры.

Может ли быть какой-либо масштабируемый и читаемый способ кодирования этих требований

Ответы [ 5 ]

0 голосов
/ 16 февраля 2019

Я бы просто пошел с:

if (param1) {
    if (param2) {
    } else {
    }
} else {
    if (param2) {
    } else {
    }
}

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

if (param1) {
    if (param2) {
        method_12();
    } else {
        method_1();
    }
} else {
    if (param2) {
        method_2();
    } else {
        method_none();
    }
}

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

0 голосов
/ 16 февраля 2019

Ваши комбинации параметров - это не более чем двоичное число (например, 01100), где 0 обозначает false и 1 a true.

Так что ваша версия можетбыть легко рассчитанным с использованием всех комбинаций единиц и нулей.Возможные комбинации с 2 входными параметрами:

  1. 11 -> оба true
  2. 10 -> первое true, второе false
  3. 01 -> первое false, второе true
  4. 00 -> оба false

Так что с этим знаниемЯ придумал довольно масштабируемое решение, использующее «битовую маску» (не более чем число) и «битовые операции»:

public static int getVersion(boolean... params) {
    int length = params.length;
    int mask = (1 << length) - 1;
    for(int i = 0; i < length; i++) {
        if(!params[i]) {
            mask &= ~(1 << length - i - 1);
        }
    }
    return mask + 1;
}

Наиболее интересная строка, вероятно, такая:

mask &= ~(1 << length - i - 1);

Он делает много вещей одновременно, я разделил это.Часть length - i - 1 вычисляет положение «бита» внутри битовой маски справа (на основе 0, как в массивах).

Следующая часть: 1 << (length - i - 1) сдвигает число 1количество позиций слева.Допустим, у нас есть позиция 3, тогда результатом операции 1 << 2 (2 - третья позиция) будет двоичное число со значением 100.

~знак является двоичным обратным, поэтому все биты инвертированы, все 0 обращены в 1 и все 1 превращены в 0.В предыдущем примере обратное значение 100 было бы 011.

Последняя часть: mask &= n такая же, как mask = mask & n, где n - это ранее вычисленное значение 011.Это не что иное, как двоичное И, поэтому все те же биты, которые находятся в mask и в n, сохраняются там, где все остальные отбрасываются.

В общем, эта единственная строка не делает ничего, кроме удаления «бита» в заданной позиции mask, если входной параметр равен false.


Еслиномера версий не последовательны от 1 до 4, тогда вам может помочь таблица поиска версий, например эта .

Весь код может потребоваться всего лишь одна настройка впоследняя строка:

return VERSIONS[mask];

Где ваш массив VERSIONS состоит из всех версий по порядку, но в обратном порядке.(индекс 0 из VERSIONS, где оба параметра имеют значение false)

0 голосов
/ 16 февраля 2019

РЕДАКТИРОВАНИЕ: Для масштабируемого решения определите версии для каждой комбинации параметров с помощью Map:

Map<List<Boolean>, Integer> paramsToVersion = Map.of(
        List.of(true, true), 1,
        List.of(false, false), 2,
        List.of(true, false), 3,
        List.of(false, true), 4);

Теперь найти правильную версию - простой поиск по карте:

    version = paramsToVersion.get(List.of(param1, param2));

Способ, которым я инициализировал карту, работает с Java 9. В старых версиях Java это немного более многословно, но, вероятно, все же стоит сделать.Даже в Java 9 вам нужно использовать Map.ofEntries, если у вас есть 4 или более параметров (для 16 комбинаций), что тоже немного многословно.

Оригинальный ответ: Мой вкус будетбыть для вложенных if / else операторов и проверять каждый параметр только один раз:

    if (param1) {
        if (param2) {
            version = 1;
        } else {
            version = 3;
        }
    } else {
        if (param2) {
            version = 4;
        } else {
            version = 2;
        }
    }

Но он плохо масштабируется по многим параметрам.

0 голосов
/ 16 февраля 2019

Я сомневаюсь, что есть способ, который был бы более компактным, читаемым и масштабируемым одновременно.

Вы выражаете условия как минимизированные выражения , которые являются компактными и могут иметьзначение (в частности, нерелевантные переменные не загромождают их).Но нет никакого систематического подхода, который вы могли бы использовать.

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

Боюсь, что бесплатный обед не предоставляется.Ваше текущее решение отлично.


Если вы стремитесь к эффективности (т.е. избегаете необходимости оценивать все выражения последовательно), тогда вы можете думать о подходе таблицы истинности, но следующим образом:

  • объявить массив номеров версий с 2 ​​^ n записями;

  • использовать код так же, как вы написали для инициализациивсе записи в таблице;чтобы достичь этого, перечислите все целые числа в [0, 2 ^ n) и используйте их двоичное представление;

  • теперь для запроса, сформируйте целочисленный индекс из n входных логических значений и ищитемассив.

Используя ответ Олевва, таблица будет [2, 4, 3, 1].Поиск будет выглядеть так (false, true) => T [01b] = 4.

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

0 голосов
/ 16 февраля 2019

Если вам нужно перечислить все возможные комбинации логических выражений, часто проще всего преобразовать их в число:

//                            param1:   F  T  F  T
//                            param2;   F  F  T  T
static final int[] VERSIONS = new int[]{2, 3, 4, 1};

...
version = VERSIONS[(param1 ? 1:0) + (param2 ? 2:0)];
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...