Великі оператори комутатора: поганий ООП?


77

Я завжди дотримувався думки, що великі оператори комутаторів є симптомом поганого дизайну ООП. Раніше я читав статті, які обговорювали цю тему, і вони пропонували альтернативні підходи на основі ООП, як правило, засновані на поліморфізмі, щоб створити правильний об'єкт для розгляду справи.

Зараз я потрапив у ситуацію, яка має монстральний оператор перемикання, заснований на потоці даних із сокета TCP, в якому протокол складається в основному з команди завершення нового рядка, за якою слідують рядки даних, а потім кінцевий маркер. Команда може бути однією із 100 різних команд, тому я хотів би знайти спосіб зменшити цю операцію перемикання монстрів до чогось більш керованого.

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

Чи існують якісь закономірності для такого роду проблем? Будь-які пропозиції щодо можливих реалізацій?

Одна думка, яку я мав, - це використовувати пошук словника, узгоджуючи текст команди з типом об’єкта для створення екземпляра. Це має приємну перевагу просто створити новий об’єкт і вставити нову команду / тип у таблицю для будь-яких нових команд.

Однак це також має проблему вибуху типу. Зараз мені потрібно 100 нових класів, а також я повинен знайти спосіб їх чистого інтерфейсу до моделі даних. Чи справді "одне справжнє твердження про перемикання"?

Буду вдячний за ваші думки, думки чи коментарі.


Як щодо використання виразу? Тож знайдіть функцію за її назвою та назвіть її. Ім'я може легко збігатися з переданими змінними, тому тоді нам не потрібно використовувати switch.
творець

Відповіді:


34

Ви можете отримати деяку користь від командного шаблону .

Що стосується ООП, ви можете згорнути кілька подібних команд, кожна в один клас, якщо варіації поведінки досить малі, щоб уникнути повного вибуху класу (так, я вже чую, як гуру ООП про це кричать). Однак, якщо система вже є ООП, і кожна з команд 100+ справді унікальна, тоді просто зробіть з них унікальні класи та скористайтесь перевагами успадкування для консолідації загальних матеріалів.

Якщо система не є ООП, то я б не додав ООП лише для цього ... Ви можете легко використовувати Шаблон команди за допомогою простого пошуку словника та покажчиків функцій, або навіть динамічно генерованих викликів функцій на основі імені команди, залежно від мову. Потім ви можете просто згрупувати логічно пов'язані функції у бібліотеки, що представляють набір подібних команд, щоб досягти керованого розділення. Я не знаю, чи є хороший термін для такого роду реалізації ... Я завжди вважаю це стилем "диспетчера", заснованим на підході MVC до обробки URL-адрес.


Погодьтеся зі словником та делегуйте ідею. Ви, ймовірно, також отримаєте покращення продуктивності, використовуючи Dictionary <string, Action> над великим оператором перемикання. Складність полягає в її ініціалізації, хоча це може бути автоматизовано за допомогою відображення, якщо потрібно.
Zooba

Можливо, ініціалізацію можна витягнути з конфігурації.
davogones

Проблема з відображенням полягає в тому, що багато веб-хостів працюють на рівні довіри, який не дозволяє вам ним користуватися :(

Цікаво, що якщо різниця між філіями полягає лише в тому, що вони є різними підкласами бази, тоді ви в основному описуєте фабрику.
Тедрік Уокер

24

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

switch (eFoo)
{
case Foo.This:
  eatThis();
  break;
case Foo.That:
  eatThat();
  break;
}

switch (eFoo)
{
case Foo.This:
  drinkThis();
  break;
case Foo.That:
  drinkThat();
  break;
}

... можливо, слід переписати як ...

IAbstract
{
  void eat();
  void drink();
}

class This : IAbstract
{
  void eat() { ... }
  void drink() { ... }
}

class That : IAbstract
{
  void eat() { ... }
  void drink() { ... }
}

Однак одне твердження switch не є настільки сильним показником, що оператор switch слід замінювати чимось іншим.


8
Проблема полягає в тому, що ви не знаєте, чи отримуєте ви через мережу команду Цей чи Той. Десь ви повинні вирішити, називати новий This () або новий That (). Після цього абстрагування з ОО - це шматок пирога.

17

Команда може бути однією із 100 різних команд

Якщо вам потрібно зробити одну із 100 різних речей, ви не можете уникнути наявності 100-бічної гілки. Ви можете кодувати його в потоці керування (перемикач, if-elseif ^ 100) або в даних (100-елементна карта від рядка до команди / заводу / стратегії). Але воно буде там.

Ви можете спробувати відокремити результат 100-сторонньої гілки від речей, яким не потрібно знати цей результат. Можливо, просто 100 різних методів це нормально; немає необхідності винаходити непотрібні вам об’єкти, якщо це робить код громіздким.


10
+1. Мені стає пригніченим слух скаржитися на смердючий перемикач, але потім пропоную альтернативу, яка насправді є просто абстрагованою поведінкою перемикачів, розмитою по всій системі, на відміну від акуратного стека.

1
Це може бути педантично, але чи справді карта (рядок-> команда) є гілкою? Концептуально це може бути, оскільки існує багато речей, які можуть статися залежно від значення змінної, але коли я думаю про розгалуження, я думаю про те, що явно перевіряє змінну на можливі значення. Тоді як карта - це прямий пошук. Іншими словами, прив'язка умови до результату виконується один раз на початку (у популяції карти), а не кожен раз (шляхом тестування змінної). Згідно з цим визначенням, карта команд видаляє гілку.
Кем Джексон

1
@CamJackson: Звичайно, якщо ви використовуєте карту, ви можете використовувати її з кодом, який сам не виконує жодного розгалуження --- він передав розгалуження на будь-яку map_lookupфункцію, яку він викликає (яка буде розгалужуватися). Я не впевнений, що це спостереження купує для вас. "Це насправді не галузь, якщо це трапляється в іншому місці"? Зв'язок між входом і виходом здійснюється під час компіляції для операторів перемикання (та інших потоків управління) та під час виконання для структур даних (найімовірніше). Це не так, як форма перемикача змінюється під час роботи. Для обох видів пошуку потрібна гілка (і одна з них). Сподіваюсь, це допоможе :-)
Йонас Кьолкер

@ JonasKölker Це хороший момент. Думаю, я не ретельно продумував фактичну структуру даних та те, як це працює. Тож єдиним реальним способом видалення гілки було б, якби ваші 100 команд зручно називались 0, 1, 2 тощо, щоб ви могли використовувати їх безпосередньо як індекси.
Кем Джексон,

@CamJackson: Я думаю, що я згоден --- у такому випадку єдине розгалуження, що відбудеться, пов'язане з тим, що те, що зберігається у вашому 100-елементному масиві, буде поміщено в лічильник програм (у Java це відбувається опосередковано і позаду сцени). Тут я визначаю розгалуження, щоб означати, що після того, як лічильник програми приймає одне конкретне значення, він може приймати кілька різних наступних значень (при звичайному виконанні програми відсутність космічних променів). (Загальний виклик: якщо ваші команди були названі 0..17 та 19..100, ви могли б мати 101-елементний масив A, де A [18] є обробником помилок. Ви можете узагальнювати далі.)
Jonas Kölker

3

Я думаю, що це один з небагатьох випадків, коли великі комутатори є найкращою відповіддю, якщо не з’явиться якесь інше рішення.


2

Я бачу схему стратегії. Якщо у мене є 100 різних стратегій ... нехай буде. Заява про гігантські перемикачі потворна. Чи всі команди є дійсними іменами класів? Якщо так, просто використовуйте імена команд як імена класів і створіть об'єкт стратегії за допомогою Activator.CreateInstance.


2

Є дві речі, які спадають на думку, коли говорите про велике твердження перемикача:

  1. Це порушує OCP - ви можете постійно підтримувати великі функції.
  2. У вас можуть бути погані показники: O (n).

З іншого боку, реалізація карти може відповідати OCP і може працювати з потенційно O (1).


1
"карта [може бути O (1)]" - якщо під час пошуку здійснюється зчитування k бітів, він може розрізняти 2 ** k різних ключів. Таким чином, для n ключів потрібно прочитати принаймні журнал (n) бітів. Як ви можете це зробити за час O (1), коли log (n) необмежений? В якій моделі?
Jonas Kölker

@ JonasKölker: Карта може бути реалізована за допомогою хеш-таблиці, за допомогою якої можна знайти правильний ключ O(1). Дивіться цей відповідь: stackoverflow.com/a/1055261/4834
quamrana

Хеш-функція все ще має обробити ці журнальні (n) біти. Хеш-таблиці (і деякі види дерева пошуку) мають значення O (1) щодо значень, що містяться, але O (n) або гірше щодо бітового розміру ключових значень.
CA McCann

4
Будь-який гідний компілятор використовує таблицю для операторів перемикання з послідовними значеннями регістрів або двійкове дерево рішень, де це не працює, або де на основі знання архітектури процесора двійкове дерево рішень швидше. Для операторів case з великою кількістю значень, що знаходяться далеко один від одного, хороший компілятор буде хеш значення, щоб зменшити його до таблиці, якщо це вигідно. Незважаючи ні на що, компілятор використовуватиме стратегію, яка реалізує оператор switch якомога швидше. Тож вибачте, але відмовлятись від твердження про перемикання з міркувань продуктивності - це дурно.
gnasher729

1

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

Я переживав це в одному проекті сам, коли все більше і більше коду входило в комутатор, поки він не став неможливим. Моє рішення полягало у визначенні класу параметрів, який містив контекст для команд (ім’я, параметри, будь-що, зібране перед перемиканням), створення методу для кожного оператора case та виклик цього методу з об’єктом параметра з справи.

Звичайно, повністю диспетчер команд OOP (заснований на магії, такій як відображення або механізми, як активація Java), є красивішим, але іноді вам просто потрібно виправити ситуацію та виконати роботу;)


1

Ви можете використовувати словник (або хеш-карту, якщо ви кодуєте на Java) (це називає Стів Макконнелл, керований таблицею).


0

Я бачу, що ви могли б покращити це, що зробить ваш код керованим даними, так, наприклад, для кожного коду ви підберете щось, що обробляє його (функцію, об'єкт). Ви також можете використовувати відображення для відображення рядків, що представляють об'єкти / функції, та вирішити їх під час виконання, але ви можете провести деякі експерименти для оцінки продуктивності.


0

Найкращий спосіб вирішити цю конкретну проблему: чисто серіалізація та протоколи - це використання IDL та генерація коду маршалінгу з операторами switch. Оскільки незалежно від того, які шаблони (фабрика прототипів, шаблон команд тощо) ви намагаєтесь використовувати інакше, вам потрібно буде якось ініціалізувати відображення між ідентифікатором команди / рядком та покажчиком класу / функції, і він буде працювати повільніше, ніж оператори switch, оскільки компілятор може використовувати ідеальний хеш-пошук для операторів перемикання.


Мені цікаво, який компілятор реалізує оператори перемикання рядків за допомогою ідеального хешування?
paxos1977

0

Так, я думаю, що великі регістри є симптомом того, що чийсь код можна вдосконалити ... як правило, застосовуючи більш об'єктно-орієнтований підхід. Наприклад, якщо я виявляю, що оцінюю тип класів у операторі switch, це майже завжди означає, що я міг би скористатися Generics для усунення оператора switch.


1
Проблема полягає в тому, що ці дані надходять з іншого місця, вони не можуть бути об’єктно-орієнтованими, оскільки надходять. Потрібно щось зробити, щоб перетворити їх на об’єктну орієнтацію, і загалом це великий перемикач.
Loren Pechtel 03

0

Ви також можете скористатися мовним підходом і визначити команди з відповідними даними в граматиці. Потім ви можете використовувати інструмент генератора для синтаксичного аналізу мови. Для цього я використав « Іронію» . В якості альтернативи ви можете використовувати шаблон Interpreter.

На мій погляд, мета не в тому, щоб побудувати найчистішу модель ОО, а в тому, щоб створити гнучку, розширювану, ремонтопридатну та потужну систему.


0

Нещодавно у мене була подібна проблема з величезною заявою на перемикач, і я позбувся негарного перемикача за допомогою найпростішого рішення таблиці пошуку та функції або методу, що повертають очікуване значення. шаблон команди - це гарне рішення, але, мабуть, мати 100 класів неприємно. так у мене було щось на зразок:

switch(id)
    case 1: DoSomething(url_1) break;
    case 2: DoSomething(url_2) break;
    ..
    ..
    case 100 DoSomething(url_100) break;

і я змінився на:

string url =  GetUrl(id);  
DoSomthing(url);

GetUrl може перейти до БД і повернути URL-адресу, яку ви шукаєте, або це може бути словник в пам'яті, що містить 100 URL-адрес. Я сподіваюся, це може допомогти кожному, хто замінює величезні жахливі заяви про перемикання.


0

Подумайте, як Windows була спочатку написана в насосі повідомлення програми. Це смоктало. Додатки працюватимуть повільніше, додавши більше опцій меню. Оскільки команда, яку шукали, закінчувалася все далі до нижньої частини оператора перемикання, все частіше чекали відповіді. Неприйнятно мати довгі оператори перемикання, крапка. Я створив демон AIX як обробник команд POS, який може обробляти 256 унікальних команд, навіть не знаючи, що знаходиться в потоці запитів, отриманих через TCP / IP. Найпершим символом потоку був індекс масиву функцій. Для будь-якого індексу, який не використовувався, встановлено обробник повідомлень за замовчуванням; увійти і попрощатися.


2
Це просто неправда. У операторах перемикачів використовується таблиця переходів, тому більше не потрібно вмикати 2 елементи, як це робиться у 2000 році. Це один пошук таблиці.
Ерік Функенбуш

Навіть коли оператор switch порівнює значення рядків?
Роберт Ахманн

Ну, очевидно, це залежить від довжини рядкового значення, але крім цього, так. Windows використовує C, який все одно не може вмикати рядки, але в C # ви можете. Це все ще таблиця пошуку.
Ерік Функенбуш
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.