2016-11-29 2 views
8

У меня есть ситуация, в которой мне нужно проверить несколько условий, когда каждая комбинация имеет другой результат. В моем конкретном состоянии у меня есть две переменные, которые являются перечисляемыми типами, каждая из которых может быть 2 разных значения.Эффективно проверять несколько условий

enum Enum1 
{ 
    COND_1, 
    COND_2 
} 
enum EnumA 
{ 
    COND_A, 
    COND_B 
} 
Enum1 var1; 
EnumA varA; 

Это дает мне 4 возможных условия, которые требуют 4 разных результатов. Я придумал несколько различных способов сделать это, либо с помощью если заявления или переключателя заявления:

if(var1 == Enum1.COND_1 && varA == EnumA.COND_A) 
{ 
    // Code 
} 
else if(var1 == Enum1.COND_1 && varA == EnumA.COND_B) 
{ 
    // Code 
} 
else if(var1 == Enum1.COND_2 && varA == EnumA.COND_A) 
{ 
    // Code 
} 
else if(var1 == Enum1.COND_2 && varA == EnumA.COND_B) 
{ 
    // Code 
} 

Или:

switch(var1) 
{ 
    case COND_1: 
     switch(varA) 
     { 
      case COND_A: 
       // Code 
       break; 
      case COND_B: 
       // Code 
       break; 
     } 
     break; 
    case COND_2: 
     switch(varA) 
     { 
      case COND_A: 
       // Code 
       break; 
      case COND_B: 
       // Code 
       break; 
     } 
     break; 
} 

Я думал о других, но не хотят для заполнения этого кода: P Я хотел бы знать, что лучший способ сделать это. Я думаю, что переключатель немного легче читать, но ifs короче. Я думаю, это было бы здорово, если бы переключатели могли иметь несколько условий, но я не слышал об этом. Это также задает вопрос: как лучше всего это сделать с произвольным числом переменных и возможными значениями?

+2

Если вы делаете это * серьезно *, то вы, вероятно, будете использовать механизм правил или другую стороннюю систему, которая отбросит весь этот код. – Kayaman

+1

Зачем вам нужно проверять несколько условий? Что вы на самом деле делаете? Это может быть решено лучше с редизайном вместо того, чтобы пытаться решить между ifs и switch. – Kayaman

+0

https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern –

ответ

3

Я предпочитаю вариант if без гнездования, так как он короткий, и у вас есть все условия в одной строке.

При остановке кода во время отладки это может стать утомительным, так как вам нужно преодолеть все предыдущие условия, которые являются O (n). При выполнении кода это не имеет значения, поскольку компилятор, вероятно, оптимизирует код.

Нет очевидного лучшего способа, поэтому вам придется немного поэкспериментировать.

1

Может быть, сумасшедшая идея, но вы можете построить int или байт с помощью флагов и использовать его в одном коммутаторе.

private int getIntegerStateForConditions(boolean... conditions){ 
    int state = 0; 
    int position = 0; 
    for(boolean condition: conditions){ 
     if(condition){ 
      state = state || (1 << position++); 
     } 
    } 
    return state; 
} 

...

switch(getIntegerStateForCondition((var1 == Enum1.COND_1), (var2 == EnumA.COND_A)){ 
    case 0: ... //both condition false 
    case 1: ... //first condition true second false 
    case 2: ... //first false, second true ... 
} 

...

Я думаю, что это очень далеко от того, чистый код, но он выглядит лучше.

1

Если бы я был ты, я бы полагаться на битовых флагов, чтобы иметь только один byte (как у вас есть только 4 случая использования), чтобы иметь дело и использовать switch заявление по этому byte управлять всеми вариантов использования.

Что-то вроде этого:

private static final int COND_2 = 1; 
private static final int COND_B = 2; 

private byte value; 

public void setValue(Enum1 enum1) { 
    if (enum1 == Enum1.COND_1) { 
     this.value &= ~COND_2; 
    } else { 
     this.value |= COND_2; 
    } 
} 

public void setValue(EnumA enumA) { 
    if (enumA == EnumA.COND_A) { 
     this.value &= ~COND_B; 
    } else { 
     this.value |= COND_B; 
    } 
} 

public Enum1 getEnum1() { 
    return (this.value & COND_2) == COND_2 ? Enum1.COND_2 : Enum1.COND_1; 
} 


public EnumA getEnumA() { 
    return (this.value & COND_B) == COND_B ? EnumA.COND_B : EnumA.COND_A; 
} 

Тогда ваши испытания будут:

switch (value) { 
    case 0 : 
     // 1-A; 
     break; 
    case 1 : 
     // 2-A; 
     break; 
    case 2 : 
     // 1-B; 
     break; 
    case 3 : 
     // 2-B; 
     break; 
} 
9

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

Stream.of(new Conditional(COND_1, COND_A,() -> {/* do something */}), 
      new Conditional(COND_1, COND_B,() -> {/* do something */}), 
      new Conditional(COND_2, COND_A,() -> {/* do something */}), 
      new Conditional(COND_2, COND_B,() -> {/* do something */})) 
     .filter(x -> x.test(var1, varA)) 
     .findAny() 
     .ifPresent(Conditional::run); 

Это потребовало бы второстепенную класс:

class Conditional implements BiPredicate<Enum1, EnumA>, Runnable 
{ 
    private final Enum1 var1; 
    private final EnumA varA; 
    private final Runnable runnable; 

    public Conditional(Enum1 var1, EnumA varA, Runnable runnable) { 
     this.var1 = var1; 
     this.varA = varA; 
     this.runnable = runnable; 
    } 

    @Override 
    public boolean test(Enum1 enum1, EnumA enumA) { 
     return var1 == enum1 && varA == enumA; 
    } 

    @Override 
    public void run() { 
     runnable.run(); 
    } 
} 
1

Я лично предпочитаю это:

if(understandableNameInContextName1(var1, varA)) 
{ 
    // Code 
} 
else if(understandableNameInContextName2(var1, varA)) 
{ 
    // Code 
} 
else if(understandableNameInContextName3(var1, varA)) 
{ 
    // Code 
} 
else if(understandableNameInContextName4(var1, varA)) 
{ 
    // Code 
} 

private boolean understandableNameInContextName1(Object var1, Object varA){ 
return (var1 == Enum1.COND_1 && varA == EnumA.COND_A); 
} 

private boolean understandableNameInContextName2(Object var1, Object varA){ 
return (var1 == Enum1.COND_1 && varA == EnumA.COND_B); 
} 

private boolean understandableNameInContextName3(Object var1, Object varA){ 
return (var1 == Enum1.COND_2 && varA == EnumA.COND_A); 
} 

private boolean understandableNameInContextName4(Object var1, Object varA){ 
return (var1 == Enum1.COND_2 && varA == EnumA.COND_B); 
} 

И имена методов может быть, isOrderShippedAndDelivered(), isRequestSendAndAckRecieved().

Причина в том, что это сделает код более понятным. Если у вас нет данных, которые вернут вас к этим операциям if, то их оптимизация не будет большой.

См: https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil

7

Разница в производительности, вероятно, ничтожна, поэтому я бы сосредоточиться на краткость и читаемость. Так что я бы просто упростить if «S немного с помощью временных переменных:

boolean is_1 = (var1 == Enum1.COND_1); 
boolean is_A = (varA == EnumA.COND_A); 

if(is_1 && is_A) 
{ 
    // Code 
} 
else if(is_1 && !is_A) 
{ 
    // Code 
} 
else if(!is_1 && is_A) 
{ 
    // Code 
} 
else if(!is_1 && !is_A) 
{ 
    // Code 
} 
3

Я определенно предпочитаю плоский вариант, он может просто использовать немного меньше дублирования:

// If you can't make the variables final, make some final copies 
final Enum1 var1 = Enum1.COND_2; 
final EnumA varA = EnumA.COND_B; 

class Tester { // You could also make an anonymous BiPredicate<Enum1, EnumA> 
    boolean t(Enum1 v1, EnumA vA) { 
     return var1 == v1 && varA == vA; 
    } 
}; 

Tester tes = new Tester(); 

if (tes.t(Enum1.COND_1, EnumA.COND_A)) { 
    // code 
} else if (tes.t(Enum1.COND_1, EnumA.COND_B)) { 
    // code 
} else if (tes.t(Enum1.COND_2, EnumA.COND_A)) { 
    // code 
} else if (tes.t(Enum1.COND_2, EnumA.COND_B)) { 
    // code 
} 

Выполнить это here. Возможно, вы можете сделать его еще короче и менее избыточным, выполнив static import of the enums, чтобы избежать упоминания имен перечислений, например. tes.t(COND_1, COND_B). Или, если вы готовы отказаться от некоторой безопасности времени компиляции, вы можете передать строку, которая преобразуется в два значения перечисления, например. tes.t("COND_1 COND_A") (реализация предоставляется читателю).

0

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