Вопрос:

Способы устранения переключения в коде

design-patterns

117435 просмотра

23 ответа

1474 Репутация автора

Как можно исключить использование ключа в коде?

Автор: Mariano Источник Размещён: 24.09.2008 10:34

Ответы (23)


13 плюса

23034 Репутация автора

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

Язык также является важным фактором и здесь, я думаю.

Автор: Bernard Размещён: 24.09.2008 10:35

21 плюса

41276 Репутация автора

Я думаю, что лучший способ - использовать хорошую карту. Используя словарь, вы можете сопоставить практически любой вход с другим значением / объектом / функцией.

Ваш код будет выглядеть примерно так (psuedo):

void InitMap(){
    Map[key1] = Object/Action;
    Map[key2] = Object/Action;
}

Object/Action DoStuff(Object key){
    return Map[key];
}
Автор: Josh Размещён: 24.09.2008 10:36

4 плюса

10874 Репутация автора

если еще

Я опровергаю предположение, что переключение по своей сути плохо, хотя.

Автор: Iain Holder Размещён: 24.09.2008 10:36

3 плюса

27724 Репутация автора

Ну, во-первых, я не знал, что использование switch - это анти-паттерн.

Во-вторых, switch всегда можно заменить на операторы if / else if.

Автор: SCdF Размещён: 24.09.2008 10:36

0 плюса

13966 Репутация автора

Самый очевидный, независимый от языка ответ - использовать серию «если».

Если используемый вами язык имеет указатели на функции (C) или функции, которые являются значениями 1-го класса (Lua), вы можете достичь результатов, аналогичных «переключению», используя массив (или список) (указателей) функций.

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

Автор: Remo.D Размещён: 24.09.2008 10:37

3 плюса

95805 Репутация автора

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

Автор: Nick Johnson Размещён: 24.09.2008 10:38

37 плюса

1710 Репутация автора

Само по себе переключение не так уж и плохо, но если у вас в ваших методах много «switch» или «if / else» для объектов, это может быть признаком того, что ваш дизайн немного «процедурный» и что ваши объекты просто ценны ковши. Переместите логику на ваши объекты, вызовите метод для ваших объектов и дайте им решить, как реагировать.

Автор: Lars Westergren Размещён: 24.09.2008 10:41

3 плюса

45293 Репутация автора

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

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

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

Автор: Skizz Размещён: 24.09.2008 10:45

1 плюс

3688 Репутация автора

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

Автор: Xavier Nodet Размещён: 24.09.2008 10:45

40 плюса

44361 Репутация автора

Переключатель - это шаблон, независимо от того, реализован ли он с помощью оператора switch, цепочки, таблицы поиска, полиморфизма или соответствия, или чего-то еще.

Вы хотите исключить использование « оператора switch » или « шаблона switch »? Первый может быть исключен, второй, только если можно использовать другой шаблон / алгоритм, и большую часть времени это невозможно или это не лучший подход для этого.

Если вы хотите исключить оператор switch из кода, первый вопрос, который нужно задать, - это где имеет смысл исключить оператор switch и использовать какой-то другой метод. К сожалению, ответ на этот вопрос зависит от конкретной области.

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

Вот несколько альтернатив для переключения оператора:

Автор: Pop Catalin Размещён: 24.09.2008 10:49

0 плюса

526 Репутация автора

Операторы Switch часто могут быть заменены хорошим дизайном OO.

Например, у вас есть класс «Учетная запись», и вы используете оператор переключения для выполнения другого расчета в зависимости от типа учетной записи.

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

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

Автор: jason Размещён: 24.09.2008 10:49

263 плюса

4143 Репутация автора

Решение

Операторы switch сами по себе не являются антипаттернами, но если вы кодируете объектно-ориентированные, вы должны подумать, лучше ли использовать переключатель с полиморфизмом, а не с помощью оператора switch.

С полиморфизмом это:

foreach (var animal in zoo) {
    switch (typeof(animal)) {
        case "dog":
            echo animal.bark();
            break;

        case "cat":
            echo animal.meow();
            break;
    }
}

становится таким:

foreach (var animal in zoo) {
    echo animal.speak();
}
Автор: mlarsen Размещён: 24.09.2008 10:49

6 плюса

7674 Репутация автора

Я думаю, что вы ищете шаблон стратегии.

Это может быть реализовано несколькими способами, которые были упомянуты в других ответах на этот вопрос, таких как:

  • Карта значений -> функции
  • Полиморфизм. (подтип объекта будет определять, как он обрабатывает конкретный процесс).
  • Функции первого класса.
Автор: AJ. Размещён: 24.09.2008 10:51

-10 плюса

4411 Репутация автора

Еще один голос за если / еще. Я не большой поклонник дел или заявлений о смене, потому что есть люди, которые их не используют. Код менее читабелен, если вы используете регистр или переключатель. Может быть, не менее читабельным для вас, но для тех, кому никогда не нужно было использовать команду.

То же самое касается объектных фабрик.

Блоки if / else - это простая конструкция, которую получают все. Есть несколько вещей, которые вы можете сделать, чтобы убедиться, что у вас нет проблем.

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

 if a = 1 then 
     do something else 
     if a = 2 then 
         do something else
     else 
         if a = 3 then 
             do the last thing
         endif
     endif 
  endif

Это действительно плохо - делай это вместо этого.

if a = 1 then 
   do something
endif 
if a = 2 then 
   do something else
endif 
if a = 3 then 
   do something more
endif 

Оптимизация будь проклята. Это не сильно влияет на скорость вашего кода.

Во-вторых, я не против выхода из блока If, если в определенном блоке кода разбросано достаточно операторов break, чтобы сделать его очевидным

procedure processA(a:int)
    if a = 1 then 
       do something
       procedure_return
    endif 
    if a = 2 then 
       do something else
       procedure_return
    endif 
    if a = 3 then 
       do something more
       procedure_return
    endif 
end_procedure

РЕДАКТИРОВАТЬ : On Switch и почему я думаю, что трудно уловить:

Вот пример оператора switch ...

private void doLog(LogLevel logLevel, String msg) {
   String prefix;
   switch (logLevel) {
     case INFO:
       prefix = "INFO";
       break;
     case WARN:
       prefix = "WARN";
       break;
     case ERROR:
       prefix = "ERROR";
       break;
     default:
       throw new RuntimeException("Oops, forgot to add stuff on new enum constant");
   }
   System.out.println(String.format("%s: %s", prefix, msg));
 }

Для меня проблема здесь в том, что нормальные управляющие структуры, которые применяются в C-подобных языках, были полностью нарушены. Существует общее правило, что если вы хотите поместить более одной строки кода в управляющую структуру, вы используете фигурные скобки или оператор начала / конца.

например

for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}

Для меня (и вы можете исправить меня, если я ошибаюсь), оператор Case выбрасывает это правило в окно. Условно исполняемый блок кода не помещается в структуру начала / конца. Из-за этого, я считаю, что Кейс концептуально достаточно отличается, чтобы его нельзя было использовать.

Надеюсь, что отвечает на ваши вопросы.

Автор: seanyboy Размещён: 24.09.2008 10:53

228 плюса

43664 Репутация автора

См. Запах Заявлений Переключателя :

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

И Рефакторинг, и Рефакторинг на Шаблоны имеют подходы для решения этой проблемы.

Если ваш (псевдо) код выглядит так:

class RequestHandler {

    public void handleRequest(int action) {
        switch(action) {
            case LOGIN:
                doLogin();
                break;
            case LOGOUT:
                doLogout();
                break;
            case QUERY:
               doQuery();
               break;
        }
    }
}

Этот код нарушает принцип Open Closed и является хрупким по отношению к каждому новому типу кода действия. Чтобы исправить это, вы можете ввести объект «Command»:

interface Command {
    public void execute();
}

class LoginCommand implements Command {
    public void execute() {
        // do what doLogin() used to do
    }
}

class RequestHandler {
    private Map<Integer, Command> commandMap; // injected in, or obtained from a factory
    public void handleRequest(int action) {
        Command command = commandMap.get(action);
        command.execute();
    }
}

Если ваш (псевдо) код выглядит так:

class House {
    private int state;

    public void enter() {
        switch (state) {
            case INSIDE:
                throw new Exception("Cannot enter. Already inside");
            case OUTSIDE:
                 state = INSIDE;
                 ...
                 break;
         }
    }
    public void exit() {
        switch (state) {
            case INSIDE:
                state = OUTSIDE;
                ...
                break;
            case OUTSIDE:
                throw new Exception("Cannot leave. Already outside");
        }
    }

Тогда вы можете ввести объект «State».

// Throw exceptions unless the behavior is overriden by subclasses
abstract class HouseState {
    public HouseState enter() {
        throw new Exception("Cannot enter");
    }
    public HouseState leave() {
        throw new Exception("Cannot leave");
    }
}

class Inside extends HouseState {
    public HouseState leave() {
        return new Outside();
    }
}

class Outside extends HouseState {
    public HouseState enter() {
        return new Inside();
    }
}

class House {
    private HouseState state;
    public void enter() {
        this.state = this.state.enter();
    }
    public void leave() {
        this.state = this.state.leave();
    }
}

Надеюсь это поможет.

Автор: toolkit Размещён: 24.09.2008 10:54

0 плюса

50470 Репутация автора

Зависит от того, почему вы хотите заменить его!

Многие интерпретаторы используют «вычисленные переходы» вместо операторов switch для выполнения кода операции.

Что мне не хватает в переключателе C / C ++, так это Pascal 'in' и диапазоны. Я также хотел бы включить строки. Но они, хотя и являются тривиальными для компилятора, являются тяжелой работой, когда они выполняются с использованием структур, итераторов и прочего. Так что, наоборот, я бы хотел заменить множество переключателей, если бы только переключатель C () был более гибким!

Автор: Will Размещён: 24.09.2008 10:56

1 плюс

43149 Репутация автора

Для C ++

Если вы имеете в виду, например, AbstractFactory, я думаю, что метод registerCreatorFunc (..) обычно лучше, чем требование добавлять регистр для каждого необходимого «нового» оператора. Затем позволяем всем классам создавать и регистрировать функцию creatorFunction (..), которая может быть легко реализована с помощью макроса (если я осмелюсь упомянуть). Я считаю, что это общий подход, который используют многие структуры. Я впервые увидел это в ET ++, и я думаю, что многие фреймворки, требующие макроса DECL и IMPL, используют его.

Автор: epatel Размещён: 24.09.2008 11:04

5 плюса

14161 Репутация автора

switch операторы было бы хорошо заменить, если вы обнаружите, что добавляете новые состояния или новое поведение в операторы:

Int State;

String getString () {
   переключатель (состояние) {
     case 0: // поведение для состояния 0
           вернуть «ноль»;
     case 1: // поведение для состояния 1
           вернуть «один»;
   }
   бросить новый IllegalStateException ();
}

double getDouble () {

   switch (this.state) {
     case 0: // поведение для состояния 0
           возврат 0d;
     case 1: // поведение для состояния 1
           возврат 1d;
   }
   бросить новый IllegalStateException ();
}

Добавление нового поведения требует копирования switch, а добавление новых состояний означает добавление другого caseв каждое switch утверждение.

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

if - elseМожно использовать сопоставление с образцом и несколько блоков, хотя на самом деле возникают те же проблемы при добавлении нового поведения и новых состояний.

Решение, которое другие предложили как «полиморфизм», является примером модели состояния :

Замените каждое из состояний своим собственным классом. Каждое поведение имеет свой метод в классе:

Состояние государства;

String getString () {
   return state.getString ();
}

double getDouble () {
   return state.getDouble ();
}

Каждый раз, когда вы добавляете новое состояние, вы должны добавить новую реализацию IStateинтерфейса. В switchмире вы бы добавили caseк каждому switch.

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

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

Автор: jamesh Размещён: 24.09.2008 11:24

1 плюс

7489 Репутация автора

В процедурном языке, таком как C, переключение будет лучше, чем любая из альтернатив.

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

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

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

Там, где не происходит явного полиморфизма, может быть целесообразно реализовать шаблон Стратегии .

Но если ваша альтернатива - большой блок IF ... THEN ... ELSE, то забудьте об этом.

Автор: Bill Michell Размещён: 24.09.2008 12:05

1 плюс

44027 Репутация автора

Используйте язык, который не поставляется со встроенным оператором switch. Perl 5 приходит на ум.

Если серьезно, почему вы хотите избежать этого? И если у вас есть веские основания избегать этого, то почему бы просто не избежать этого?

Автор: innaM Размещён: 24.09.2008 12:16

1 плюс

13383 Репутация автора

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

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

Это отличный пример «разделяй и властвуй»:

Скажем, у вас есть какой-то переводчик.

switch(*IP) {
    case OPCODE_ADD:
        ...
        break;
    case OPCODE_NOT_ZERO:
        ...
        break;
    case OPCODE_JUMP:
        ...
        break;
    default:
        fixme(*IP);
}

Вместо этого вы можете использовать это:

opcode_table[*IP](*IP, vm);

... // in somewhere else:
void opcode_add(byte_opcode op, Vm* vm) { ... };
void opcode_not_zero(byte_opcode op, Vm* vm) { ... };
void opcode_jump(byte_opcode op, Vm* vm) { ... };
void opcode_default(byte_opcode op, Vm* vm) { /* fixme */ };

OpcodeFuncPtr opcode_table[256] = {
    ...
    opcode_add,
    opcode_not_zero,
    opcode_jump,
    opcode_default,
    opcode_default,
    ... // etc.
};

Обратите внимание, что я не знаю, как удалить избыточность opcode_table в C. Возможно, я должен задать вопрос об этом. :)

Автор: Cheery Размещён: 24.09.2008 03:37

0 плюса

1122 Репутация автора

Переключение не очень хороший способ, поскольку он нарушает принцип Open Close. Вот как я это делаю.

public class Animal
{
       public abstract void Speak();
}


public class Dog : Animal
{
   public virtual void Speak()
   {
       Console.WriteLine("Hao Hao");
   }
}

public class Cat : Animal
{
   public virtual void Speak()
   {
       Console.WriteLine("Meauuuu");
   }
}

А вот как это использовать (принимая ваш код):

foreach (var animal in zoo) 
{
    echo animal.speak();
}

В основном то, что мы делаем, - делегирование ответственности дочернему классу вместо того, чтобы родитель решал, что делать с детьми.

Вы также можете прочитать «Принцип замещения Лискова».

Автор: Sheraz Размещён: 25.11.2008 06:11

0 плюса

39879 Репутация автора

В JavaScript используется ассоциативный массив:
this:

function getItemPricing(customer, item) {
    switch (customer.type) {
        // VIPs are awesome. Give them 50% off.
        case 'VIP':
            return item.price * item.quantity * 0.50;

            // Preferred customers are no VIPs, but they still get 25% off.
        case 'Preferred':
            return item.price * item.quantity * 0.75;

            // No discount for other customers.
        case 'Regular':
        case
        default:
            return item.price * item.quantity;
    }
}

становится таким:

function getItemPricing(customer, item) {
var pricing = {
    'VIP': function(item) {
        return item.price * item.quantity * 0.50;
    },
    'Preferred': function(item) {
        if (item.price <= 100.0)
            return item.price * item.quantity * 0.75;

        // Else
        return item.price * item.quantity;
    },
    'Regular': function(item) {
        return item.price * item.quantity;
    }
};

    if (pricing[customer.type])
        return pricing[customer.type](item);
    else
        return pricing.Regular(item);
}

учтивость

Автор: Premraj Размещён: 01.07.2015 08:15
Вопросы из категории :
32x32