Способы устранения кода в коде
Каковы способы устранения использования переключателя в коде?
Ответы
Ответ 1
Операторы switch не являются антипаттером как таковым, но если вы кодируете объектно-ориентированный объект, вам следует подумать, лучше ли использовать коммутатор с помощью polymorphism вместо использования оператора 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();
}
Ответ 2
См. Зависимость запаздывания операторов:
Как правило, аналогичные операторы switch разбросаны по всей программе. Если вы добавляете или удаляете предложение в одном коммутаторе, вам часто приходится находить и ремонтировать других.
Оба Рефакторинг и Рефакторинг для шаблонов имеют подходы к разрешите это.
Если ваш (псевдо) код выглядит следующим образом:
class RequestHandler {
public void handleRequest(int action) {
switch(action) {
case LOGIN:
doLogin();
break;
case LOGOUT:
doLogout();
break;
case QUERY:
doQuery();
break;
}
}
}
Этот код нарушает Открытый закрытый принцип и является хрупким для каждого нового типа кода действия, который приходит.
Чтобы исправить это, вы можете ввести объект 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();
}
}
Надеюсь, что это поможет.
Ответ 3
Сам переключатель не так уж плох, но если у вас много "переключателей" или "если/еще" на объектах в ваших методах, это может быть признаком того, что ваш дизайн немного "процедурный" и что ваши объекты являются только ведрами стоимости. Переместите логику в свои объекты, вызовите метод на своих объектах и дайте им возможность решить, как реагировать.
Ответ 4
Коммутатор - это шаблон, который реализуется с помощью оператора switch, если есть целая цепочка, таблица поиска, полиморфизм oop, сопоставление шаблонов или что-то еще.
Вы хотите исключить использование оператора оператора "или" шаблона переключения "? Первый можно устранить, второй - только в том случае, если можно использовать другой шаблон/алгоритм, и большую часть времени это невозможно, или это не лучший подход для этого.
Если вы хотите исключить оператор оператора switch из кода, первый вопрос, который задают, - это где есть смысл устранить оператор switch и использовать какую-либо другую технику. К сожалению, ответ на этот вопрос является специфичным для домена.
И помните, что компиляторы могут выполнять различные оптимизации для переключения операторов. Так, например, если вы хотите эффективно обрабатывать сообщения, оператор switch в значительной степени подходит. Но, с другой стороны, выполнение бизнес-правил на основе оператора switch, вероятно, не самый лучший способ, и приложение должно быть перехвачено.
Вот несколько альтернатив для оператора switch:
Ответ 5
Я думаю, что лучший способ - использовать хорошую карту. Используя словарь, вы можете отобразить почти любой ввод в другое значение/объект/функцию.
ваш код будет выглядеть как-то (psuedo) следующим образом:
void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}
Object/Action DoStuff(Object key){
return Map[key];
}
Ответ 6
Все любят ОГРОМНЫЕ блоки if else
. Так легко читать! Мне любопытно, почему вы хотите удалить инструкции switch. Если вам нужен оператор switch, вам, вероятно, понадобится оператор switch. Серьезно, хотя, я бы сказал, это зависит от того, что делает код. Если все, что делает коммутатор, это вызов функций (скажем), вы можете передать указатели на функции. Является ли это лучшим решением спорным.
Язык также является важным фактором здесь.
Ответ 7
Я думаю, что то, что вы ищете, это шаблон стратегии.
Это может быть реализовано несколькими способами, о которых упоминалось в других ответах на этот вопрос, например:
- Карта значений → функции
- полиморфизм. (подтип объекта будет определять, как он обрабатывает определенный процесс).
- Функции первого класса.
Ответ 8
Операторы switch
были бы хороши для замены, если вы обнаружите, что добавляете новые состояния или новое поведение в выражения:
int state;
String getString() {
switch (state) {
case 0 : // behaviour for state 0
return "zero";
case 1 : // behaviour for state 1
return "one";
}
throw new IllegalStateException();
}
double getDouble() {
switch (this.state) {
case 0 : // behaviour for state 0
return 0d;
case 1 : // behaviour for state 1
return 1d;
}
throw new IllegalStateException();
}
Добавление нового поведения требует копирования switch
, а добавление новых состояний означает добавление другого case
в каждый оператор switch
.
В Java вы можете переключать очень ограниченное число примитивных типов, значения которых вы знаете во время выполнения. Это само по себе представляет проблему: состояния представлены как магические числа или символы.
Соответствие шаблону, и можно использовать несколько блоков if - else
, хотя действительно имеют те же проблемы при добавлении новых типов поведения и новых состояний.
Решение, которое другие предложили как "полиморфизм", является примером шаблона состояния :
Замените каждое из состояний своим классом. Каждое поведение имеет свой собственный класс в классе:
IState state;
String getString() {
return state.getString();
}
double getDouble() {
return state.getDouble();
}
Каждый раз, когда вы добавляете новое состояние, вам нужно добавить новую реализацию интерфейса IState
. В мире switch
вы добавляете case
к каждому switch
.
Каждый раз, когда вы добавляете новое поведение, вам нужно добавить новый метод в интерфейс IState
и каждую из реализаций. Это те же бремя, что и раньше, хотя теперь компилятор проверяет, что у вас есть реализации нового поведения в каждом предварительно существующем состоянии.
Другие уже заявили, что это может быть слишком тяжеловесным, так что, конечно, есть точка, куда вы достигаете места, где вы переходите от одного к другому. Лично, второй раз, когда я пишу переключатель, это точка, в которой я рефакторинг.
Ответ 9
если-иначе
Я опровергаю предположение, что коммутатор по своей сути плохой.
Ответ 10
Ну, во-первых, я не знал, что использование переключателя было анти-шаблоном.
Во-вторых, переключатель всегда можно заменить на команды if/else if.
Ответ 11
Почему вы хотите? В руках хорошего компилятора оператор switch может быть намного эффективнее, чем блоки if/else (а также быть более легким для чтения), и только самые большие переключатели, скорее всего, будут ускорены, если их заменят какие-либо типы структуры данных непрямого поиска.
Ответ 12
'switch' - это просто языковая конструкция, и все языковые конструкции можно рассматривать как инструменты для выполнения работы. Как и в случае с реальными инструментами, некоторые инструменты лучше подходят для одной задачи, чем другие (вы не использовали бы кувалду, чтобы поднять крючок изображения). Важная часть заключается в том, как определяется "выполнение задания". Нужно ли его поддерживать, нужно ли быстро, нужно ли масштабировать, нужно ли его расширять и т.д.
В каждой точке процесса программирования обычно используется ряд конструкций и шаблонов, которые можно использовать: переключатель, последовательность if-else-if, виртуальные функции, таблицы перехода, карты с указателями функций и т.д. С опытом программист инстинктивно узнает правильный инструмент для использования в данной ситуации.
Следует полагать, что любой, кто поддерживает или просматривает код, по крайней мере так же квалифицирован, как и оригинальный автор, так что любая конструкция может быть безопасно использована.
Ответ 13
Если переключатель предназначен для различения различных объектов, возможно, вам не хватает некоторых классов для точного описания этих объектов или некоторых виртуальных методов...
Ответ 14
Для С++
Если вы имеете в виду ie AbstractFactory, я считаю, что метод registerCreatorFunc (..) обычно лучше, чем требовать добавления случая для каждого нового "нового" утверждения, которое необходимо. Затем, позволяя всем классам создавать и регистрировать creatorFunction (..), которые могут быть легко реализованы с помощью макроса (если я осмелюсь упомянуть). Я считаю, что это общий подход. Я впервые увидел его в ET ++, и я думаю, что многие фреймворки, которые требуют макросы DECL и IMPL, используют его.
Ответ 15
Используйте язык, который не поставляется со встроенным оператором switch. Perl 5 приходит на ум.
Серьезно, хотя, почему вы хотите избежать этого? И если у вас есть все основания, чтобы избежать этого, почему бы просто не избежать этого?
Ответ 16
Указатели функций - один из способов замены огромного оператора коротких переключателей, они особенно хороши на языках, где вы можете захватывать функции по их именам и создавать с ними материал.
Конечно, вы не должны форсировать инструкции операторов из своего кода, и всегда есть шанс, что вы делаете все это неправильно, что приводит к глупому избыточному фрагменту кода. (Это неизбежно иногда, но хороший язык должен позволять вам удалять избыточность, оставаясь чистым.)
Это отличный пример divide & conquer:
Скажем, у вас есть какой-то интерпретатор.
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. Возможно, я должен задать вопрос об этом.:)
Ответ 17
Самый очевидный, независимый от языка ответ - использовать серию "if".
Если язык, на котором вы используете, имеет указатели на функции (C) или имеет функции, которые являются значениями первого класса (Lua), вы можете достичь результатов, подобных "переключателю", используя массив (или список) функций (указателей).
Вы должны быть более конкретным на этом языке, если хотите получить более качественные ответы.
Ответ 18
Операторы Switch часто могут быть заменены хорошей конструкцией OO.
Например, у вас есть класс Account, и с помощью оператора switch выполняется другой расчет, основанный на типе учетной записи.
Я бы предположил, что это должно быть заменено несколькими классами учетных записей, представляющими разные типы учетных записей и все реализующие интерфейс учетной записи.
Затем переключатель становится ненужным, так как вы можете обрабатывать все типы учетных записей одинаково и благодаря полиморфизму, соответствующий тип расчета будет запущен для типа учетной записи.
Ответ 19
Зависит от того, почему вы хотите его заменить!
Многие интерпретаторы используют "вычисленные gotos" вместо операторов switch для выполнения кода операции.
То, что я скучаю по C/С++, - это Pascal 'in' и диапазоны. Я также хотел бы включить строки. Но они, хотя и тривиальны для компилятора, чтобы поесть, - это тяжелая работа, когда они выполняются с использованием структур и итераторов и вещей. Итак, напротив, есть много вещей, которые я бы хотел заменить переключателем, если только C-переключатель() был более гибким!
Ответ 20
В процедурном языке, таком как C, тогда переключатель будет лучше, чем любой из альтернатив.
В объектно-ориентированном языке почти всегда доступны другие альтернативы, которые лучше используют структуру объекта, особенно полиморфизм.
Проблема с операторами switch возникает, когда несколько очень похожих блоков переключателей происходят в нескольких местах приложения, а поддержка нового значения должна быть добавлена. Разработчику довольно часто приходится забывать добавлять поддержку нового значения к одному из блоков коммутатора, разбросанных по всему приложению.
При полиморфизме новый класс заменяет новое значение, а новое поведение добавляется как часть добавления нового класса. Поведение в этих точках переключения затем либо унаследовано от суперкласса, либо переопределяется для обеспечения нового поведения, либо реализовано во избежание ошибки компилятора, когда супер-метод является абстрактным.
В тех случаях, когда не существует очевидного полиморфизма, вполне может быть реализовано Стратегия.
Но если ваша альтернатива - большой IF... THEN... ELSE блок, а затем забудьте об этом.
Ответ 21
Переключатель не является хорошим способом, так как он разрывает принцип "Открыть Закрыть". Вот как я это делаю.
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();
}
В основном, мы делаем делегирование ответственности за дочерний класс вместо того, чтобы родитель решал, что делать с детьми.
Вы также можете прочитать "Принцип замены Лискова".
Ответ 22
В JavaScript с использованием ассоциативного массива:
это:
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);
}
Предоставлено
Ответ 23
Если вы ищете
Создание объекта для условия (Альтернатива для Factory_method)
ИЛИ
Выполнить определенный блок кода на основе условия (Загрузить a Strategy)
У меня есть решение.
- Использование Reflection API: создать новый экземпляр класса, передав className
- Используя классName как параметр: Предварительно заполните карту с возможными классами = > ClassName VS Object. Передайте имя класса и получите соответствующий объект с карты и используйте его.
Один пример доступен в шаблонах проектирования (Factory) @
Шаблоны проектирования: Factory vs Factory метод vs Аннотация Factory
Ответ 24
Еще одно голосование за if/else. Я не большой поклонник случаев или операторов switch, потому что есть люди, которые их не используют. Код менее читабельен, если вы используете случай или переключатель. Может быть, не менее читаемым для вас, а тем, кому никогда не приходилось использовать эту команду.
То же самое касается объектов-объектов.
Если/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, если существует достаточно предложений о разрывах, разбросанных по конкретному блоку кода, чтобы сделать его очевидным.
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
РЕДАКТИРОВАТЬ: на коммутаторе и почему я думаю, что это сложно сделать:
Вот пример оператора 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-подобных языках, были полностью нарушены. Существует общее правило, что если вы хотите разместить более одной строки кода внутри структуры управления, вы используете фигурные скобки или инструкцию begin/end.
например.
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
Для меня (и вы можете исправить меня, если я ошибаюсь), оператор Case выводит это правило из окна. Условно выполненный блок кода не помещается внутри структуры начала/конца. Из-за этого я считаю, что Case концептуально другой, чтобы его не использовали.
Надеюсь, что ответит на ваши вопросы.