Большие заявления переключателя: плохой ООП?


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

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

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

есть ли какие-либо шаблоны для такого рода проблемы? Есть предложения по возможным реализациям?

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

однако, это также имеет проблему типа взрыва. Теперь мне нужно 100 новых классов, плюс я должен найти способ связать их чисто с моделью данных. Является ли" один истинный оператор switch " действительно способом идти?

Я был бы признателен за ваши мысли, мнения или комментарии.

14 71

14 ответов:

вы можете получить некоторую выгоду от Команда.

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

Если система не ООП, то я бы не добавил ООП только для этого... вы можете легко использовать шаблон команды с помощью простого поиска словаря и указателей функций или даже динамически генерируемых вызовов функций на основе имени команды, в зависимости от языка. Затем вы можете просто сгруппировать логически связанные функции в библиотеки, которые представляют собой набор подобных команд для достижения приемлемого разделения. Я не знаю, есть ли хороший термин для такого рода реализации... Я всегда думаю об этом как о стиле" диспетчера", основанном на MVC-подходе к обработке URL-адресов.

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

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 должен быть заменен с чем-то еще.

команда может быть одной из 100 различных команд

Если вам нужно сделать одну из 100 разных вещей, вы не можете избежать 100-полосной ветви. Вы можете кодировать его в потоке управления (switch, if-elseif^100) или в данных (100-элементная карта от строки до команды/фабрики/стратегии). Но она будет там.

вы можете попытаться изолировать результат ветви 100-way от вещей, которые не должны знать этот результат. Может быть, просто 100 различных методы в порядке; нет необходимости изобретать объекты, которые вам не нужны, если это делает код громоздким.

Я думаю, что это один из немногих случаев, когда большие переключатели являются лучшим ответом, если не появится какое-то другое решение.

Я вижу шаблон стратегии. Если у меня есть 100 разных strategies...so да будет так. Заявление о гигантском переключателе уродливо. Являются ли все команды допустимыми именами классов? Если это так, просто используйте имена команд в качестве имен классов и создайте объект стратегии с активатором.Метод createinstance.

есть две вещи, которые приходят на ум, когда речь идет о крупном операторе switch:

  1. это нарушает OCP-вы можете постоянно поддерживать большую функцию.
  2. у вас может быть плохая производительность: O(n).

с другой стороны, реализация карты может соответствовать OCP и может выполняться с потенциально O(1).

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

Я сам испытал это в одном проекте, когда все больше и больше кода входило в коммутатор, пока он не стал недостижимым. Мое решение состояло в том, чтобы определить класс параметров, который содержал контекст для команд (имя, параметры, что угодно, собранные перед переключателем), создать метод для каждого оператора case и вызвать этот метод с параметром объекта из дела.

конечно, полностью диспетчер команд ООП (основанный на магии, такой как отражение или механизмы, такие как активация Java) более красив, но иногда вы просто хотите исправить ситуацию и выполнить работу ;)

вы можете использовать словарь (или хэш-карту, если вы кодируете на Java) (это называется table driven development от Steve McConnell).

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

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

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

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

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

у меня недавно была аналогичная проблема с огромным заявлением switch, и я избавился от уродливого переключателя самым простой решение a таблица и функция или метод, возвращающие ожидаемое значение. шаблон команды-хорошее решение, но наличие 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-адресов. Я надеюсь, что это может помочь кому-нибудь там при замене огромных чудовищных заявлений switch.

подумайте о том, как Windows была первоначально написана в приложении message pump. Это отстой. Приложения будут работать медленнее с дополнительными опциями меню, которые вы добавили. По мере того, как поиск команды заканчивался все дальше и дальше в нижней части оператора switch, ожидание ответа становилось все более продолжительным. Недопустимо иметь длинные операторы переключения, период. Я сделал демон AIX в качестве обработчика команд POS, который мог обрабатывать 256 уникальных команд, даже не зная, что было в поток запросов, полученный по протоколу TCP / IP. Самый первый символ потока был индексом в массиве функций. Любой индекс не используется, был установлен обработчик сообщений по умолчанию; войти и попрощаться.