Избегайте switch! Используйте enum!
Недавно мне пришлось проводить рефакторинг некоторого кода и я заметил, что большая часть кода состояла из структур switch-case или if-else каскадов. И тогда я вспомнил статью Дэниэла и решил воспользоваться его четырьмя правилами для того чтобы избавиться от этого безобразия. Допустим у нас есть конструкции вида:
switch (value) {
case SOME_CONSTANT:
//do something
break;
case SOME_OTHER_CONSTANT:
//do something else
break;
...
default:
//do something totally different
break;
}
или эквивалентные if-else каскады.
Во первых давайте предположим, что константы использованные выше это некоторые перечисления. Например:
public enum Status {
ACTIVE,
INACTIVE,
UNKNOWN;
}
Аналогичный switch-case выглядел бы таким образом:
switch (getState()) {
case INACTIVE:
//do something
break;
case ACTIVE:
//do something else
break;
case UNKNOWN:
//do something totally different
break;
}
В этом случае нам вообще не нужны switch-case. Мы можем сделать все использовав только перечисление.
public enum Status {
INACTIVE {
public void doSomething() {
//do something
}
},
ACTIVE {
public void doSomething() {
//do something else
}
},
UNKNOWN {
public void doSomething() {
//do something totally different
}
};
public abstract void doSomething();
}
И тогда switch-case сократиться до:
getState().doSomething();
Но что делать если константы определенны в чужом коде. Давайте оптимизируем наш код для этого случая.
public static final int INACTIVE = 0; public static final int ACTIVE = 1; public static final int UNKNOWN = 2;
Кажется мы вернулись туда откуда пришли. А вот и нет. Все что нам нужно это сделать так:
Status.values()[getState()].doSomething();
В этом случае есть некоторые проблемы на которые вы должны обратить внимание. Enum.values () возвращает элементы массива в том порядке в котором они были определены. Поэтому убедитесь, что порядок определения соответствует каждой из констант. Кроме того убедитесь, что вы не солкнетесь с исключением ArrayOutOfBoundsException. Подсказка: это еще один повод не зыбыть добавить тесты.
Существует еще один случай, что может произойти. Давайте представим, что константы определенны не в строгом порядке, как мы это сделали ранее:
public static final int INACTIVE = 4; public static final int ACTIVE = 7; public static final int UNKNOWN = 12;
Для покрытия этого случая нам нужно видоизменить перечисления примерно так:
public enum Status {
INACTIVE(4),
ACTIVE(7),
UNKNOWN(12);
private int state;
public static Status getStatusFor(int desired) {
for (Status status : values()) {
if (desired == status.state) {
return status;
}
}
//perform error handling here
//e.g. throw an exception or return UNKNOWN
}
}
Даже если это включает один if, (нарушается правило №4), то все ранво это выглядит намного лучше, чем нагромождение switch-case или if-else каскадов. Подсказка: не забываем про тесты.
Как вы относитесь к этой технике? Применяли ли вы её когда-либо в своей работе и каких результатов добивались?