Карта функций против оператора switch


21

Я работаю над проектом, который обрабатывает запросы, и в запросе есть два компонента: команда и параметры. Обработчик для каждой команды очень прост (<10 строк, часто <5). Существует не менее 20 команд, и, скорее всего, их будет более 50.

Я придумал пару решений:

  • один большой переключатель / if-else в командах
  • карта команд для функций
  • отображение команд на статические классы / синглтоны

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

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

Я придумал следующий список за / против для каждого:

переключатель

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

команды карты -> функция

  • профи
    • маленькие кусочки размером с укус
    • можно добавлять / удалять команды программно
  • минусы
    • если сделано в линии, то же самое визуально как переключатель
    • если не сделано in-line, множество функций используется только в одном месте

команды карты -> статический класс / синглтон

  • профи
    • может использовать полиморфизм для простой проверки ошибок (только как 3 строки, но все же)
    • аналогичные преимущества для карты -> функциональное решение
  • минусы
    • много очень маленьких классов будут загромождать проект
    • реализация не все в одном месте, так что не так просто сканировать реализации

Дополнительные примечания:

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

Команда - это строка, но я могу легко сопоставить ее с числом, если это удобно. Сигнатура функции выглядит примерно так:

Reply Command(List<String> params)

В Go есть функции верхнего уровня, а на других платформах, которые я рассматриваю, также есть функции верхнего уровня, отсюда и разница между вторым и третьим вариантами.


8
сопоставить команды с функциями и загрузить их во время выполнения из конфигурации. используйте шаблон команды.
Стивен Эверс

3
Не бойтесь создавать множество маленьких функций. Обычно набор из нескольких небольших функций более удобен в обслуживании, чем одна огромная функция.
Барт ван Инген Шенау

8
Что с этим перевернутым миром, где люди отвечают на вопросы в комментариях и просят больше информации в ответах?
PDR

4
@ SteveEvers: если это не требует доработки, это ответ, независимо от того, насколько коротким. Если это произойдет, и у вас нет времени или чего-то еще, оставьте это для кого-то другого, чтобы ответить (всегда хочется обмануть, чтобы написать ответ, который подтверждает комментарий, который уже имеет полдюжины голосов). Лично я чувствую, что это требует доработки, ОП действительно хочет знать, почему лучшее решение - это лучшее решение.
PDR

1
@pdr - верно. Я склонялся к отображению команд в функции, но я относительно младший программист на курсе по дизайну CS. Моему профессору нравится много занятий, поэтому есть как минимум 2 законных решения. Я хотел знать фаворита сообщества.
beatgammit

Ответы:


14

Это отлично подходит для карты (2-е или 3-е предлагаемое решение). Я использовал его десятки раз, и это просто и эффективно. Я действительно не различаю эти решения; Важным моментом является то, что есть карта с именами функций в качестве ключей.

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

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

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


4

Ваша проблема очень хорошо сочетается с шаблоном проектирования Command . Таким образом, в основном у вас будет базовый Commandинтерфейс, а затем будет несколько CommandImplклассов, которые будут реализовывать этот интерфейс. Интерфейс по существу должен иметь только один метод doCommand(Args args). Вы можете передать аргументы через экземпляр Argsкласса. Таким образом, вы используете силу полиморфизма вместо неуклюжих операторов if / else. Также эта конструкция легко расширяема.


3

Всякий раз, когда я спрашиваю себя, должен ли я использовать оператор switch или полиморфизм в стиле OO, я обращаюсь к проблеме выражения . По сути, если у вас есть разные «кейсы» для ваших данных и палочка для поддержки разных «действий» (где каждое действие делает что-то свое для каждого кейса), то действительно сложно создать систему, которая, естественно, позволяет добавлять как новые кейсы, так и новые действия. в будущем.

Если вы используете операторы switch (или шаблон Visitor), тогда легко добавлять новые действия (потому что вы видите все в одной функции), но трудно добавлять новые случаи (потому что вам нужно вернуться и отредактировать старые функции).

И наоборот, если вы используете полиморфизм в стиле OO, легко добавлять новые случаи (просто создать новый класс), но сложно добавлять методы в интерфейс (потому что тогда вам нужно вернуться назад и отредактировать кучу классов)

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


Кстати, с точки зрения того, насколько расширяемые вещи не имеют большого значения, используете ли вы классы или функции. Если мы сравниваем с оператором switch, то важно то, как вещи «отправляются», а классы и функции отправляются одинаково. Поэтому просто используйте все, что более удобно в вашем языке (а поскольку в Go есть лексическая область видимости и замыкания, различие между классами и функциями на самом деле очень мало).

Например, вы обычно можете использовать делегирование для проверки ошибок вместо того, чтобы полагаться на наследование (мой пример в Javascript, потому что O не знает синтаксис Go, надеюсь, вы не против)

function make_command(real_command){
    return function(x){
        if(check_arguments(x)){
            return real_command(x);
        }else{
            //handle error here
        }
    }
 }

 command1 = make_command(function(x){ 
     //do something
 })

 command2 = make_command(function(x){
     //do something else
 })

 command1(17);
 commnad2(42);

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

tl; dr: нет лучшего способа решить все проблемы. С точки зрения «заставить все работать» сфокусируйтесь на создании абстракций таким образом, чтобы обеспечить соблюдение важных инвариантов и защитить вас от ошибок. С точки зрения «будущего» имейте в виду, какие части кода с большей вероятностью будут расширены.


1

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

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

Тогда у меня была бы фабрика, которая знает, как обнаружить эти классы во время выполнения, изменяя их для загрузки из разных сборок и т. Д. Это означает, что вам не нужны все они в одном проекте.

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


0

Как вы выбираете свои команды / функции?

Если есть какой-то «умный» способ выбора правильной функции, то это путь - означает, что вы можете добавить новую поддержку (возможно, во внешнюю библиотеку) без изменения логики ядра.

Кроме того, тестирование отдельных функций проще изолированно, чем громадный оператор switch.

И, наконец, использование только в одном месте - вы можете обнаружить, что, как только вы доберетесь до 50, разные биты различных функций могут быть повторно использованы?


Команды являются уникальными строками. Я могу сопоставить их с целыми числами, если это необходимо.
beatgammit

0

Я понятия не имею, как работает Go, но архитектура, которую я использовал в ActionScript, состоит в том, чтобы иметь двусвязный список, который действует как цепь ответственности. Каждая ссылка имеет функцию defineResponsibility (которую я реализовал как обратный вызов, но вы можете написать каждую ссылку отдельно, если она лучше работает для того, что вы пытаетесь сделать). Если ссылка определила, что она несет ответственность, она вызовет meetResponsibility (опять же, обратный вызов), и это приведет к прекращению цепочки. Если он не несет ответственности, он передает запрос следующему звену в цепочке.

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.