Як переробляти додаток із кількома випадками комутації?


10

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

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

Як ти гадаєш?


2
Яка актуальна проблема з поточним кодом?
Філіп Кендалл

Що відбувається, коли вам доведеться внести одну з цих змін? Чи потрібно додати switchрегістр і викликати попередньо існуючий метод у вашій складній системі, або вам доведеться вигадувати і метод, і його виклик?
Кіліан Фот

@KilianFoth Я успадкував цей проект як розробник технічного обслуговування, і ще не довелося вносити жодних змін. Однак я скоро внесу зміни, тому хочу зараз переробити його. Але щоб відповісти на ваше запитання, так останньому.
Чауборті Каушик

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

1
@KaushikChakraborty: тоді складіть приклад із пам’яті. Бувають ситуації, коли убер-комутатор 250+ випадків підходить, і бувають випадки, коли комутатор поганий, незалежно від того, наскільки мало випадків. Чорт у деталях, і у нас немає деталей.
whatsisname

Відповіді:


13

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

Я люблю поліморфізм. Я люблю СОЛІД. Я люблю чисте об’єктно-орієнтоване програмування. Я ненавиджу бачити, що вони отримують погану відповідь, оскільки вони застосовуються догматично.

Ви не зробили гарного випадку, щоб переробити стратегію. Рефакторинг, до речі, має назву. Це називається Замінити умовне поліморфізмом .

Я знайшов для вас кілька відповідних порад від c2.com :

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

У вас є перемикач з 50 корпусів, і ваша альтернатива - виготовити 50 об'єктів. Ой і 50 рядків коду будівництва об'єкта. Це не прогрес. Чому ні? Оскільки цей рефакторинг не робить нічого для зменшення числа з 50. Ви використовуєте цей рефакторинг, коли виявите, що вам потрібно створити ще одне повідомлення переключення на тому ж вході десь в іншому місці. Ось тоді цей рефакторинг допомагає, оскільки він перетворює 100 назад на 50.

Поки ви посилаєтесь на "перемикач" так, як це єдиний у вас є, я не рекомендую цього. Єдиною перевагою, яку ви отримаєте від рефакторингу зараз, є те, що це зменшує шанси на те, що якийсь goofball скопіює та вставить ваш 50-ти вимикач.

Я рекомендую уважно придивитися до цих 50 випадків щодо спільності, які можна визначити. Я маю на увазі 50? Дійсно? Ви впевнені, що вам потрібно стільки справ? Ви можете намагатися зробити тут багато.


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

2
Я можу отримати 50, не порушуючи високої згуртованості, і зберігати речі самостійно. Я просто не можу це зробити з одним номером. Мені знадобиться 2, 5 і ще 5. Ось чому це називається факторинг. Серйозно, подивіться на всю свою архітектуру і побачите, чи не можете ви знайти вихід із цього пекла 50 справ, в якому ви знаходитесь. Рефакторинг - це скасування поганих рішень. Не увічнюючи їх у нових формах.
candied_orange

Тепер, якщо ви бачите спосіб зменшити 50 за допомогою цього рефакторингу, перейдіть на це. Щоб скористатися ідеєю Doc Browns: Карта карт може містити дві клавіші. Щось подумати.
candied_orange

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

@Candied "Ви використовуєте цей рефакторинг, коли виявите, що вам потрібно створити ще одне повідомлення перемикача на тому ж вході десь інше. Чи можете ви це розробити? Оскільки у мене є подібний випадок, коли у мене є випадки перемикання, але на різних шарах, як у нас спочатку авторизація, валідація, процедури CRUD, потім дао. Отже, у кожному шарі є випадки комутації на одному вході, тобто ціле число, але виконують різні функції, такі як auth, дійсні. тож ми повинні створити одну класну ялинку кожного типу, яка має різні методи? Чи вписується наш випадок у те, що ви намагаєтесь сказати, "повторюючи той же перемикач на одному вході"?
Сіддхарт Триха

9

Карта об'єктів стратегії тільки, яка ініціалізується в якійсь функції вашого коду, де у вас є кілька рядків коду

     myMap.Add(1,new Strategy1());
     myMap.Add(2,new Strategy2());
     myMap.Add(3,new Strategy3());

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

     case 1:
          MyClass1.Doit1(someParameters);
          break;
     case 2:
          MyClass2.Doit2(someParameters);
          break;
     case 3:
          MyClass3.Doit3(someParameters);
          break;

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

  • ініціалізація карти тепер стає відокремленою від диспетчерського коду, який фактично викликає функцію, пов'язану з певним номером, і останній більше не містить цих 50 повторень, це буде просто схоже myMap[number].DoIt(someParameters). Тому цей код відправки не потрібно торкатися кожного разу, коли надходить нове число, і він може бути реалізований за принципом відкритого закриття. Більше того, коли ви отримаєте вимоги, коли вам потрібно розширити сам код відправки, вам більше не доведеться міняти 50 місць, а лише одне.

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

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


Поки я не хочу сліпо замінювати кожен перемикач поліморфізмом, я скажу, що використання карти так, як пропонує доктор Браун тут, працювало для мене дуже добре в минулому. При реалізації такої ж інтерфейс , будь ласка , замінити Doit1, Doit2і т.д. з одним Doitметодом , який має багато різних реалізацій.
candied_orange

І якщо у вас є контроль над типом вхідного символу, який використовується як ключ, ви можете піти на крок далі, зробивши doTheThing()метод символу введення. Тоді вам не потрібно підтримувати карту.
Кевін Крумвіде

1
@KevinKrumwiede: те, що ви пропонуєте, означає просто передавати самі об’єкти стратегії в програмі, як заміну цілих чисел. Однак, коли програма приймає ціле число як вхід з якогось зовнішнього джерела даних, принаймні в одному місці системи повинно бути відображення від цілого числа до відповідної стратегії .
Doc Brown

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

@DocBrown Ось до чого я звертався, "якщо ти маєш контроль над типом символу введення".
Кевін Крумвіде,

0

Я категорично за стратегію, викладену у відповіді @DocBrown .

Я збираюся запропонувати відповідь покращити.

Дзвінки

 myMap.Add(1,new Strategy1());
 myMap.Add(2,new Strategy2());
 myMap.Add(3,new Strategy3());

може поширюватися. Вам не потрібно повертатися до того ж файлу, щоб додати ще одну стратегію, яка ще краще дотримується принципу Open-Closed.

Скажімо, ви реалізуєте Strategy1у файлі Strategy1.cpp. У вас може бути наступний блок коду.

namespace Strategy1_Impl
{
   struct Initializer
   {
      Initializer()
      {
         getMap().Add(1, new Strategy1());
      }
   };
}
using namespace Strategy1_Impl;

static Initializer initializer;

Ви можете повторити один і той же код у кожному файлі StategyN.cpp. Як бачите, це буде багато повторюваного коду. Щоб зменшити дублювання коду, ви можете використовувати шаблон, який можна помістити у файл, доступний для всіх Strategyкласів.

namespace StrategyHelper
{
   template <int N, typename StrategyType> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new StrategyType());
      }
   };
}

Після цього єдине, що вам потрібно використовувати у Strategy1.cpp:

static StrategyHelper::Initializer<1, Strategy1> initializer;

Відповідний рядок у StrategyN.cpp:

static StrategyHelper::Initializer<N, StrategyN> initializer;

Ви можете перейти до використання шаблонів на інший рівень, використовуючи шаблон класу для конкретних класів стратегії.

class Strategy { ... };

template <int N> class ConcreteStrategy;

А потім замість цього Strategy1використовуйте ConcreteStrategy<1>.

template <> class ConcreteStrategy<1> : public Strategy { ... };

Змініть клас помічників для реєстрації Strategys на:

namespace StrategyHelper
{
   template <int N> struct Initializer
   {
      Initializer()
      {
         getMap().Add(N, new ConcreteStrategy<N>());
      }
   };
}

Змініть код у Strateg1.cpp на:

static StrategyHelper::Initializer<1> initializer;

Змініть код у StrategN.cpp на:

static StrategyHelper::Initializer<N> initializer;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.