Большие заявления переключателя: плохой ООП?
Я всегда придерживался мнения, что большие операторы switch являются симптомом плохого дизайна ООП. В прошлом я читал статьи, которые обсуждают эту тему, и они предоставили альтернативные подходы на основе ООП, обычно основанные на полиморфизме, чтобы создать экземпляр правильного объекта для обработки дела.
теперь я нахожусь в ситуации, в которой есть чудовищный оператор switch, основанный на потоке данных из сокета TCP, в котором протокол состоит из в основном завершенной команды newline, далее следуют строки данных, за которыми следует конечный маркер. Команда может быть одной из 100 различных команд, поэтому я хотел бы найти способ уменьшить этот оператор Monster switch до чего-то более управляемого.
Я сделал некоторые googling, чтобы найти решения, которые я помню, но, к сожалению, Google стал пустошью нерелевантных результатов для многих видов запросов в эти дни.
есть ли какие-либо шаблоны для такого рода проблемы? Есть предложения по возможным реализациям?
одна из моих мыслей заключалась в том, чтобы использовать поиск по словарю, сопоставляя текст команды с типом объекта для создания экземпляра. Это имеет хорошее преимущество, просто создавая новый объект и вставляя новую команду/тип в таблицу для любых новых команд.
однако, это также имеет проблему типа взрыва. Теперь мне нужно 100 новых классов, плюс я должен найти способ связать их чисто с моделью данных. Является ли" один истинный оператор switch " действительно способом идти?
Я был бы признателен за ваши мысли, мнения или комментарии.
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:
- это нарушает OCP-вы можете постоянно поддерживать большую функцию.
- у вас может быть плохая производительность: 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. Самый первый символ потока был индексом в массиве функций. Любой индекс не используется, был установлен обработчик сообщений по умолчанию; войти и попрощаться.