Подтвердить что ты не робот

Каков наилучший способ заменить или заменить if..else if..else деревья в программах?

Этот вопрос мотивирован тем, что я в последнее время начал видеть слишком часто, структуру if..else if..else. Несмотря на то, что он прост и использует его, что-то об этом постоянно говорит мне снова и снова, что его можно заменить чем-то более тонким, элегантным и, как правило, легче поддерживать актуальность.

Чтобы быть как можно более конкретным, это то, что я имею в виду:

if (i == 1) {
    doOne();
} else if (i == 2) {
    doTwo();
} else if (i == 3) {
    doThree();
} else {
    doNone();
}

Я могу придумать два простых способа переписать это: либо тройным (что является еще одним способом написания одной и той же структуры):

(i == 1) ? doOne() : 
(i == 2) ? doTwo() :
(i == 3) ? doThree() : doNone();

или используя Map (в Java и я думаю, что на С# тоже) или Словарь или любая другая структура K/V, как это:

public interface IFunctor() {
    void call();
}

public class OneFunctor implemets IFunctor() {
    void call() {
        ref.doOne();
    }
}

/* etc. */    

Map<Integer, IFunctor> methods = new HashMap<Integer, IFunctor>();
methods.put(1, new OneFunctor());
methods.put(2, new TwoFunctor());
methods.put(3, new ThreeFunctor());
/* .. */
(methods.get(i) != null) ? methods.get(i).call() : doNone();

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

Итак, какой другой - и, скорее всего, лучше - замените if..else if..else и какой из них ваш любимый?

Ваши мысли ниже этой строки!


Хорошо, вот ваши мысли:

Во-первых, самым популярным ответом был оператор switch, например:

switch (i) {
    case 1:  doOne(); break;
    case 2:  doTwo(); break;
    case 3:  doThree(); break;
    default: doNone(); break;
}

Это работает только для значений, которые могут использоваться в коммутаторах, что, по крайней мере, в Java, является весьма ограничивающим фактором. Разумеется, приемлемо для простых случаев.

Другой и, возможно, немного более интересный способ, который, как вам кажется, заключается в том, чтобы сделать это, используя полиморфизм. Лекция Youtube, связанная с CMS, - отличные часы, посмотрите здесь: "Чистые коды кодов - наследование, полиморфизм и тестирование" Как насколько я понял, это переведет на что-то вроде этого:

public interface Doer {
    void do();
}

public class OneDoer implements Doer {
    public void do() {
        doOne();
    }
}
/* etc. */

/* some method of dependency injection like Factory: */
public class DoerFactory() {
    public static Doer getDoer(int i) {
        switch (i) {
            case 1: return new OneDoer();
            case 2: return new TwoDoer();
            case 3: return new ThreeDoer();
            default: return new NoneDoer();
        }
    }
}

/* in actual code */

Doer operation = DoerFactory.getDoer(i);
operation.do();

Два интересных момента из разговора Google:

  • Используйте Null Objects вместо того, чтобы возвращать null (и, пожалуйста, бросайте только Runtime Exceptions)
  • Попробуйте написать небольшой проект, если: s.

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

Спасибо всем за ответы (пока), я думаю, что, возможно, сегодня что-то узнал!

4b9b3361

Ответ 1

Эти конструкции часто могут быть заменены полиморфизмом. Это даст вам более короткий и менее хрупкий код.

Ответ 2

Оператор switch:

switch(i)
{
  case 1:
    doOne();
    break;

  case 2:
    doTwo();
    break;

  case 3:
    doThree();
    break;

  default:
    doNone();
    break;
}

Ответ 3

В объектно-ориентированных языках обычно используется полиморфизм для замены if.

Мне понравился этот Google Clean Code Talk, который охватывает тему:

Чистые кодовые разговоры - наследование, полиморфизм и тестирование

РЕЗЮМЕ

Является ли ваш код полным для операторов if? Выключить инструкции? У тебя есть такое же выражение оператора в различных места? Когда вы вносите изменения, вы сделать то же самое изменение к тому же, если/переключатель в нескольких места? Вы когда-нибудь забыли его?

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

Ответ 4

В этом вопросе есть две части.

Как отправить на основе значения? Используйте оператор switch. Он наиболее четко показывает ваше намерение.

Когда отправляется на основе значения? Только в одном месте за значение: создайте полиморфный объект, который знает, как обеспечить ожидаемое поведение для значения.

Ответ 5

Используйте переключатель/футляр для очистки: p

Ответ 6

switch, конечно, намного красивее, чем все те, если и else.

Ответ 7

В этом простом случае вы можете использовать переключатель.

В противном случае подход, основанный на таблицах, выглядит отлично, это будет мой второй выбор, когда условия достаточно регулярны, чтобы сделать его применимым, особенно когда число случаев велико.

Полиморфизм был бы вариантом, если не так уж много случаев, а условия и поведение нерегулярны.

Ответ 8

Вне использования оператора switch, который может быть быстрее, ничего. Если Else понятен и легко читается. вынуждены искать вещи на карте, обфускации вещей. Зачем делать код более трудным для чтения?

Ответ 9

switch (i) {
  case 1:  doOne(); break;
  case 2:  doTwo(); break;
  case 3:  doThree(); break;
  default: doNone(); break;
}

Набрав это, я должен сказать, что в вашем операторе if не так уж много ошибок. Как сказал Эйнштейн: "Сделайте это как можно проще, но не проще".

Ответ 10

В зависимости от типа вещи, которую вы делаете, если..., попробуйте создать иерархию объектов и использовать полиморфизм. Например:

class iBase
{
   virtual void Foo() = 0;
};

class SpecialCase1 : public iBase
{
   void Foo () {do your magic here}
};

class SpecialCase2 : public iBase
{
   void Foo () {do other magic here}
};

Затем в вашем коде просто вызовите p- > Foo(), и произойдет правильное.

Ответ 11

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

В случаях, когда doX() всегда возвращает true.

i==1 && doOne() || i==2 && doTwo() || i==3 && doThree()

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

Вы также можете назначать задания.

i==1 && (ret=1) || i==2 && (ret=2) || i==3 && (ret=3)

Как instad написания

if(a==2 && b==3 && c==4){
    doSomething();
else{
    doOtherThings();
}

Запись

a==2 && b==3 && c==4 && doSomething() || doOtherThings();

И в тех случаях, когда не уверен, что возвращает функция, добавьте || 1: -)

a==2 && b==3 && c==4 && (doSomething()||1) || doOtherThings();

Я все еще нахожу его более быстрым, чем использовать все эти if-else, и он наверняка пугает всех новых noob. Представьте себе полную страницу такого утверждения с 5 уровнями отступов.

"если" редко встречается в некоторых моих кодах, и я дал ему название "if-less programming": -)

Ответ 12

Пример, заданный в вопросе, достаточно тривиальен, чтобы работать с простым коммутатором. Проблема возникает, когда if-elses вкладываются глубже и глубже. Они больше не "понятны или легко читаемы" (как утверждал кто-то другой), и добавление нового кода или исправление ошибок в них становится все труднее и труднее быть уверенным, потому что вы можете не оказаться там, где ожидали, если логика является сложным.

Я видел, что это случалось много раз (переключатели вложенные 4 уровня в глубину и сотни строк - невозможно поддерживать), особенно внутри классов factory, которые пытаются сделать слишком много для слишком большого числа разных несвязанных типов.

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

Ответ 13

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

Ответ 14

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

Ответ 15

Я бы зашел так далеко, чтобы сказать, что ни одна программа не должна когда-либо использовать. Если вы это делаете, вы просите о неприятностях. Вы никогда не должны предполагать, что это не X, это должен быть Y. Ваши тесты должны проверяться для каждого отдельно и не выполняться после таких тестов.

Ответ 16

В python я бы написал ваш код как:

actions = {
           1: doOne,
           2: doTwo,
           3: doThree,
          }
actions[i]()

Ответ 17

Я рассматриваю эти конструкции if-elseif -... как "шум по ключевому слову". Хотя может быть ясно, что он делает, он недостаточно лаконичен; Я рассматриваю лаконизм как важную часть читаемости. Большинство языков предоставляют что-то вроде инструкции switch. Создание карты - это способ получить что-то подобное на языках, которые их не имеют, но это, безусловно, похоже на обходное решение, и есть немного накладных расходов (оператор switch переводит на некоторые простые операции сравнения и условные переходы, но карта сначала создается в памяти, затем запрашивается, и только тогда происходит сравнение и скачок).

В Common Lisp встроены две конструкции коммутатора, cond и case. cond допускает произвольные условия, а case проверяет только на равенство, но более кратким.

(cond ((= i 1)
       (do-one))
      ((= i 2)
       (do-two))
      ((= i 3)
       (do-three))
      (t
       (do-none)))

(случай я     (1 (сделай один))     (2 (до-два))     (3 (до-три))     (в противном случае (do-none)))

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

В Perl вы можете использовать оператор for, необязательно с произвольной меткой (здесь: switch):

SWITCH: for ($i) {
    /1/ && do { do_one; last SWITCH; };
    /2/ && do { do_two; last SWITCH; };
    /3/ && do { do_three; last SWITCH; };
    do_none; };

Ответ 18

Использовать тернарный оператор!

Тернарный оператор (53 символа):

i===1?doOne():i===2?doTwo():i===3?doThree():doNone();

Если (108Characters):

if (i === 1) {
doOne();
} else if (i === 2) {
doTwo();
} else if (i === 3) {
doThree();
} else {
doNone();
}

Переключатель ((ДАЛЕЕ ДЛИТЕЛЬНО, ЕСЛИ!?!?) 114 Символы):

switch (i) {
case 1: doOne(); break;
case 2: doTwo(); break;
case 3: doThree(); break;
default: doNone(); break;
}

это все, что вам нужно! это только одна строка, и она довольно аккуратная, путь короче, чем переключатель, и если!

Ответ 20

Метод Map - это лучшее, что есть. Это позволяет вам инкапсулировать заявления и очень хорошо разбираться. Полиморфизм может дополнять его, но его цели несколько отличаются. Он также вводит ненужные деревья классов.

Коммутаторы имеют недостаток отсутствующих операторов break и проваливаются, и действительно поощряют не разбивать проблему на более мелкие части.

Это сказано: маленькое дерево if..else в порядке (на самом деле, я спорил в пользу в течение нескольких дней, когда у вас есть 3, если... вместо использования Map недавно). Когда вы начинаете вводить в них более сложную логику, это становится проблемой из-за ремонтопригодности и удобочитаемости.

Ответ 21

Если у вас действительно есть куча тестов, и вы хотите делать разные вещи, когда тест верен, я бы рекомендовал цикл while только с ifs-no else. Каждый, если выполняет тест, вызывает метод, затем выходит из цикла. Нет ничего хуже, чем куча сложенных if/else/if/else и т.д.