Які способи усунути використання перемикача в коді?
Які способи усунути використання перемикача в коді?
Відповіді:
Переклади-оператори самі по собі не є антипатерном, але якщо ви орієнтуєтесь на об'єкт, орієнтований на об'єкт, вам слід подумати, чи краще використовувати комутатор за допомогою поліморфізму замість використання оператора перемикання.
З поліморфізмом це:
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();
}
typeof
, і ця відповідь не пропонує способів чи причин обходити оператори перемикання в інших ситуаціях.
Дивіться повідомлення про вимикання Запах :
Зазвичай подібні заяви комутатора розкидані по всій програмі. Якщо ви додаєте або видаляєте пропозицію в одному комутаторі, вам часто доводиться знаходити і ремонтувати інші.
І Рефакторинг, і Рефакторинг на шаблони мають підходи до вирішення цього питання.
Якщо ваш (псевдо) код виглядає так:
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");
}
}
Тоді ви можете ввести об’єкт "Держава".
// 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();
}
}
Сподіваюся, це допомагає.
Map<Integer, Command>
не потребує комутатора?
Комутатор - це шаблон, реалізований із оператором перемикання, якщо це ще ланцюжок, таблиця пошуку, поліморфізм oop, відповідність шаблону чи щось інше.
Ви хочете усунути використання " заяви переключення " або " схеми переключення "? Перший може бути усунутий, другий, лише якщо можна використовувати інший шаблон / алгоритм, і більшу частину часу це неможливо або це не кращий підхід до цього.
Якщо ви хочете усунути оператор перемикання з коду, перше запитання - де має сенс усунути оператор перемикання та використовувати якусь іншу техніку. На жаль, відповідь на це питання залежить від домену.
І пам’ятайте, що компілятори можуть робити різні оптимізації для переключення операторів. Так, наприклад, якщо ви хочете ефективно виконувати обробку повідомлень, оператор перемикання є значною мірою. Але, з іншого боку, ділові правила, засновані на операторі перемикання, - це, мабуть, не найкращий спосіб, і додаток слід перестановити.
Ось кілька альтернатив для переключення оператора:
Переключення саме по собі не так вже й погано, але якщо у вас багато методів "перемикання" або "якщо / ще" на ваших методах, це може бути ознакою того, що ваш дизайн трохи "процедурний" і що ваші об'єкти є просто цінними відра. Перемістіть логіку до своїх об'єктів, застосуйте метод на ваших об'єктах і нехай вони вирішать, як відповісти натомість.
Я думаю, що найкращий спосіб - це використовувати хорошу карту. За допомогою словника ви можете зіставити майже будь-який вхід на якесь інше значення / об'єкт / функцію.
ваш код виглядатиме так (psuedo) приблизно так:
void InitMap(){
Map[key1] = Object/Action;
Map[key2] = Object/Action;
}
Object/Action DoStuff(Object key){
return Map[key];
}
Всі люблять ВЕЛИЧЕЗНІ if else
блоки. Так легко читати! Мені цікаво, чому ви хочете видалити заяви переключення. Якщо вам потрібна оператор перемикання, вам, ймовірно, потрібна оператор перемикання. Якщо серйозно, я б сказав, що це залежить від того, що робить код. Якщо всі комутатори виконують виклики функцій (скажімо), ви можете передавати покажчики функцій. Будь то краще рішення - дискусійний.
Мова є важливим фактором і тут, я думаю.
Я думаю, що те, що ви шукаєте, - це Стратегічний шаблон.
Це може бути здійснено різними способами, про які було сказано в інших відповідях на це питання, таких як:
switch
Висловлювання було б добре замінити, якщо ви виявите, що ви додаєте нові стани або нову поведінку до заяв:
інт стан; Рядок getString () { перемикач (стан) { випадок 0: // поведінка для стану 0 повернути "нуль"; випадок 1: // поведінка для стану 1 повернути «один»; } кинути нову IllegalStateException (); } подвійний getDouble () { перемикач (this.state) { випадок 0: // поведінка для стану 0 повернути 0d; випадок 1: // поведінка для стану 1 повернути 1d; } кинути нову IllegalStateException (); }
Додавання нової поведінки вимагає копіювання switch
, а додавання нових станів означає додавання іншого case
до кожного switch
оператора.
На Java ви можете переключити лише обмежену кількість примітивних типів, значення яких ви знаєте під час виконання. Це представляє проблему саме по собі: держави представляються у вигляді магічних чисел або символів.
if - else
Можуть бути використані відповідність шаблонів та декілька блоків, хоча вони дійсно мають однакові проблеми при додаванні нових форм поведінки та нових станів.
Рішення, яке інші запропонували як "поліморфізм", є прикладом моделі держави :
Замініть кожен із штатів власним класом. Кожна поведінка має свій метод на уроці:
Стан держави; Рядок getString () { return state.getString (); } подвійний getDouble () { return state.getDouble (); }
Кожен раз, коли ви додаєте новий стан, ви повинні додавати нову реалізацію IState
інтерфейсу. У switch
світі ви додасте case
до кожного switch
.
Кожен раз, коли ви додаєте нову поведінку, вам потрібно додавати новий метод в IState
інтерфейс і кожну з реалізацій. Це той самий тягар, що і раніше, хоча зараз компілятор перевірить, чи є у вас реалізації нової поведінки для кожного попереднього стану.
Інші вже говорили, що це може бути надмірно важкою вагою, тому, звичайно, є точка, куди ви переходите, переходячи від одного до іншого. Особисто я вдруге пишу перемикач - це точка, в якій я рефактор.
якщо ще
Я спростовую припущення, що перемикач по суті є поганим.
Чому ти хочеш? У руках хорошого компілятора оператор перемикання може бути набагато ефективнішим, ніж блоки if / else (а також їх легше читати), і тільки найбільші комутатори, ймовірно, будуть прискорені, якщо їх замінить будь-який вид структури даних непрямого пошуку.
"перемикач" - це лише мовна конструкція, і всі мовні конструкції можна розглядати як інструменти для виконання роботи. Як і у випадку з реальними інструментами, деякі інструменти краще підходять до однієї задачі, ніж інші (ви б не використовували кувалду для розміщення гачка для малюнка). Важливою частиною є те, як визначено "виконання роботи". Чи потрібно це бути ремонтом, чи потрібно бути швидким, чи потрібно масштабувати, чи потрібно воно розширюватися тощо?
У кожній точці процесу програмування зазвичай є низка конструкцій та шаблонів, які можна використовувати: комутатор, послідовність if-else-if, віртуальні функції, таблиці стрибків, карти з функціональними покажчиками тощо. Маючи досвід, програміст інстинктивно знатиме правильний інструмент, який потрібно використовувати для даної ситуації.
Слід вважати, що кожен, хто підтримує або переглядає код, принаймні такий же кваліфікований, як і оригінальний автор, щоб будь-яку конструкцію можна було безпечно використовувати.
Якщо перемикач існує для розрізнення різного роду об’єктів, вам, мабуть, не вистачає деяких класів, щоб точно описати ці об'єкти або деякі віртуальні методи ...
Для C ++
Якщо ви маєте на увазі, наприклад, AbstractFactory, я думаю, що метод registerCreatorFunc (..), як правило, краще, ніж вимагати додавати регістр для кожного "нового" заяви, який необхідний. Тоді дозволяючи всім класам створювати та реєструвати CreatorFunction (..), який можна легко реалізувати за допомогою макросу (якщо я зважусь згадати). Я вважаю, що це загальний підхід, який застосовують багато рамок. Я вперше побачив це в ET ++, і я думаю, що багато рамок, для яких потрібен макрос DECL та IMPL, використовують його.
На процедурній мові, як-от C, перемикання буде кращим за будь-яку з альтернатив.
У об'єктно-орієнтованій мові майже завжди існують інші альтернативи, які краще використовують структуру об'єкта, зокрема поліморфізм.
Проблема з операторами перемикання виникає, коли декілька дуже подібних блоків комутації трапляються в декількох місцях програми, і потрібно додати підтримку нового значення. Досить часто розробник забув додати підтримку нового значення одному з блоків комутацій, розкиданих навколо програми.
При поліморфізмі новий клас замінює нове значення, а нова поведінка додається як частина додавання нового класу. Поведінка в цих точках комутації потім або успадковується від надкласу, переосмислюється для надання нової поведінки, або реалізується, щоб уникнути помилки компілятора, коли супер метод абстрактний.
Там, де очевидний поліморфізм не відбувається, це цілком може бути вартим реалізації стратегії .
Але якщо ваша альтернатива - це велике, якщо ... ЦЕ ... блок ELSE, тоді забудьте про це.
Покажчики функцій - це один із способів замінити величезний чіткий вимикач вимикачів, вони особливо гарні на мовах, де ви можете захоплювати функції за їхніми іменами та створювати з ними речі.
Звичайно, вам не слід вимушувати вимикати заяви з вашого коду, і завжди є шанс, що ви все зробите не так, що призводить до дурних зайвих фрагментів коду. (Іноді це неминуче, але добра мова повинна дозволяти вам видалити надмірність, залишаючись чистими.)
Це чудовий приклад розділення та перемоги:
Скажіть, у вас є якийсь перекладач.
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_ у C. Можливо, я повинен поставити питання про це. :)
Найбільш очевидна, не залежна від мови відповідь - використовувати серію "якщо".
Якщо мова, якою ви користуєтесь, має функціональні покажчики (C) або має функції, що є значеннями першого класу (Lua), ви можете досягти результатів, подібних до "перемикача", використовуючи масив (або список) функцій (покажчики на).
Ви повинні бути більш конкретними щодо мови, якщо хочете отримати кращі відповіді.
Заяви комутаторів часто можуть бути замінені хорошим дизайном OO.
Наприклад, у вас є клас облікового запису і виписку перемикання використовуєте для виконання іншого обчислення залежно від типу облікового запису.
Я б запропонував, щоб це було замінено на декілька класів облікових записів, що представляють різні типи облікових записів та всі реалізовані інтерфейс облікового запису.
Потім перемикач стає непотрібним, оскільки ви можете ставитись до всіх типів рахунків однаково, і завдяки поліморфізму буде проведено відповідний розрахунок для типу рахунку.
Залежить, чому ви хочете його замінити!
Багато інтерпретаторів використовують "обчислені готи" замість операторів перемикання для виконання коду.
Те, що мені не вистачає при перемиканні C / C ++, - це паскаль "в" і діапазони. Я також хочу, щоб я міг переключитися на струни. Але це, хоч і банально для компілятора їсти, - це важка робота, коли це зроблено за допомогою структур, ітераторів і речей. Отже, навпаки, є багато речей, які я хотів би замінити перемикачем, якби тільки перемикач C () був більш гнучким!
Перемикання - не гарний спосіб пройти, оскільки він порушує принцип відкриття закрити. Ось як я це роблю.
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();
}
В основному те, що ми робимо, - це делегувати відповідальність на дитячий клас, а не батьки вирішувати, що робити з дітьми.
Ви також можете прочитати "Принцип заміни Ліскова".
У 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);
}
Ще один голос за "якщо". Я не є великим прихильником заяв про випадки чи переключення, оскільки є люди, які їх не використовують. Код є менш читабельним, якщо ви використовуєте регістр або перемикач. Можливо, не менш читабельні для вас, але для тих, хто ніколи не потребував використання команди.
Те саме стосується об’єктних заводів.
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, якщо є достатньо тверджень про перерви, розкидані по конкретному кодовому блоку, щоб зробити це очевидним
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
EDIT : Увімкніть перемикач, і чому я думаю, що це важко:
Ось приклад заяви переключення ...
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));
}
Для мене питання тут полягає в тому, що нормальні структури управління, які застосовуються на мовах С, як ці мови, були повністю порушені. Існує загальне правило, що якщо ви хочете розмістити більше одного рядка коду всередині керуючої структури, ви використовуєте дужки або оператор початку / кінця.
напр
for i from 1 to 1000 {statement1; statement2}
if something=false then {statement1; statement2}
while isOKtoLoop {statement1; statement2}
Для мене (і ви можете виправити мене, якщо я помиляюся), випадок Case викидає це правило з вікна. Умовно виконаний блок коду не розміщується всередині структури початку / кінця. Через це я вважаю, що Case досить концептуально відрізняється, щоб його не використовувати.
Сподіваємось, що відповість на ваші запитання.