Крупные операторы Switch: Bad OOP?

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

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

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

Существуют ли какие-либо шаблоны для такого рода проблем? Любые предложения о возможных реализациях?

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

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

Буду признателен за ваши мысли, мнения или комментарии.

Ответ 1

Вы можете получить некоторую выгоду из Command Pattern.

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

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

Ответ 2

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

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 должен быть заменен чем-то другим.

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

Я вижу стратегию. Если у меня будет 100 различных стратегий... пусть будет так. Операция гигантского переключателя является уродливой. Все ли действующие классные имена? Если это так, просто используйте имена команд в качестве имен классов и создайте объект стратегии с помощью Activator.CreateInstance.

Ответ 6

При обсуждении большого оператора switch возникают две вещи:

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

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

Ответ 7

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

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

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

Ответ 8

Вы можете использовать словарь (или хеш-карту, если вы кодируете на Java) (она называется табличной разработкой Стив Макконнелл).

Ответ 9

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

Ответ 14

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