Рекомендуемое перечисление вместо switch
Переключатель / регистр – это общая структура управления, реализованная в большинстве обязательных языков программирования. Переключатель считается более читабельным, чем серия if / else.
Вот простой пример:
Вот список основных проблем в этом коде:
- Связь между литералами int (1, 2, 3) и исполняемым кодом не очевидна.
- Если одно из значений (например, 2) больше не поддерживается и этот переключатель не обновляется, соответственно, он навсегда будет содержать неиспользованный код.
- Если вводится новое возможное значение c (например, 4), а переключатель не обновляется, соответственно, код, вероятно, вызовет исключение UnsupportedOperationException во время выполнения без каких-либо уведомлений во время компиляции.
- Такая структура переключателей имеет тенденцию дублироваться в коде несколько раз, что делает проблемы 2 и 3 еще более сложными.
Первое простейшее исправление можно сделать, используя int-константы вместо литералов. Во-первых, давайте определим константы:
Теперь код будет выглядеть так:
(Очевидно, что в реальной жизни имена констант должны быть информативными)
Этот фрагмент более читабелен, но все остальные недостатки по-прежнему актуальны. Следующая попытка улучшить исходный фрагмент кода использует enums введенные в язык Java в версии 5 в 2004 году. Давайте определим следующее enum :
Теперь фрагмент переключателя будет немного изменен:
Этот код немного лучше: он выдаст ошибку компиляции, если один из элементов будет удален из enum Action . Однако это не приведет к ошибке компиляции, если в enum Action добавлен дополнительный элемент. Некоторые IDE или инструменты статического анализа кода могут выдавать предупреждение в этом случае, но кто обращает внимание на предупреждения? К счастью, enum может объявить абстрактный метод, который должен быть реализован каждым элементом:
Теперь оператор switch можно заменить одной строкой:
Это решение не имеет перечисленных выше недостатков:
- Это читабельно. Метод «привязан» к элементу enum ; можно написать столько javadoc сколько нужно, если смысл метода неясен. Код, который вызывает метод, тривиален: что может быть проще, чем вызов метода?
- enum удалить константу enum без удаления реализации, поэтому неиспользуемый код не останется, если некоторые функции больше не будут актуальны.
- Новый элемент enum нельзя добавить без реализации метода action() . Код без реализации не может быть скомпилирован.
- Если требуется несколько действий, все они могут быть реализованы в enum. Как мы уже упоминали, код, вызывающий определенную функцию, тривиален, поэтому теперь нет дублирования кода.
Вывод
Хотя структура switch / case хорошо известна и широко используется в различных языках программирования, ее использование может вызвать много проблем. Решение, которое использует перечисления java и описано выше, не имеет этих недостатков. Следующая статья из этой серии показывает, как расширить функциональность существующего enum .
Опубликовано на Java Code Geeks с разрешения Александра Радзина, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Избранные перечисления вместо switch
Мнения, высказанные участниками Java Code Geeks, являются их собственными.
Как красиво избавиться от switch-case посредством перечисления
Привет, Хабр! Применение switch-case в коде — давняя тема холиваров на форумах на предмет чистоты кода. Лично я склоняюсь к простому мнению: инструмент необходимо использовать по назначению.
Сегодня хотелось бы рассмотреть несколько простых кейсов, где switch-case является не лучшим выбором и предложить красивое и удобное решение проблемы.
Итак, непосредственно, кейс: от значения одной переменной зависит значение другой переменной.
Исходные данные: программа, имитирующая зоопарк. Она содержит несколько животных, представленных в виде перечисления Animal, и пару работников, описанных интерфейсом ZooWorker.
Задача: научить работников кормить животных (по сути — реализовать интерфейс ZooWorker). Алгоритм действия очень прост — необходимо определить название корма, которым питается животное, и вывести в консоль сообщение о том, что животное покормлено и чем именно оно покормлено.
Первый вариант написан на java 11. Он является самым громоздким и выглядит следующим образом:
Данное решение имеет ряд проблем:
В случае добавления животного в перечисление, требуется дописать и вышеуказанный код.
Разработчику никто не напомнит, что это нужно сделать. Т.е., если зоопарк разрастётся до сотен тысяч строк, вполне можно забыть, что при добавлении животного в перечисление необходимо еще и дописать код. В конце концов, это приведет к ошибке (и хорошо, если определено поведение default, в этом случае, по крайней мере, есть возможность быстро определить проблемное место).
При большом количестве животных switch-case сильно разрастётся.
Ну, и известная проблема switch-case в java 11 – бесконечный break, который легко пропустить.
Существует возможность немного отрефакторить вышеописанный пример и избавиться от проблемы №4 следующим образом:
Выглядит лучше, однако, другие проблемы остаются.
Начиная с java 14 и выше появилась возможность использовать более удобный формат switch-case. Представленное выше решение в новом формате будет выглядеть следующим образом:
Помимо избавления от break, решилась проблема №2: если разработчик добавит в перечисление животное, но не определит необходимое поведение в switch-case, код просто не скомпилируется. Уже лучше, однако, третья и первая проблемы все ещё остаются.
В последнем решении перенесём зависимость переменных непосредственно в перечисление. Для этого немного изменим его:
Теперь можно реализовать работника всего в одну строку:
В представленном решении, при добавлении элемента в перечисление, нет необходимости дописывать код, и разработчик точно не забудет нигде ничего дописать. К тому же, код не будет превращаться в лапшу при увеличении количества элементов в перечислении.
Попробуем немного расширить и усложнить наш кейс: теперь от значения одной переменной зависит не только значение другой, но и последующий алгоритм действий.
Задача остается той же — научить работников кормить животных. Однако, теперь для того, чтобы покормить животное, необходимо не просто определить требуемый корм, но и рассчитать его объем. Для этого изменим интерфейс ZooWorker:
Для большей наглядности представим, что расчёт корма не везде производится простым умножением на коэффициент, а используется некая формула:
Для бегемотов: [количество бегемотов^2]
Для филинов: [количество филинов * 3]
Для пингвинов: [(количество пингвинов ^ 3)/2]
Для обезьян: [количество обезьян * 10]
Ниже представлены решения по уже используемым шаблонам:
Решение switch-case на java 11
Немного переработанный код будет выглядеть следующим образом:
Для решения через enum требуется доработать перечисление:
И, собственно, сам работник:
Перейдем к заключительному примеру. Предположим, расчёт корма представляет собой сложную логику, которую не получится передать в качестве лямбды.
В этом случае для каждого животного создадим отдельного сотрудника, который будет кормить только его.
Представим, как решить задачу «покормить всех животных» «в лоб»: например, в вызывающем классе собираем список (множество) всех работников, и получаем возможность по очереди вызвать метод feed у элементов списка. Вышло бы что-то вроде этого:
Выглядит неплохо, однако, что делать, если потребуется отдельный метод для каждого животного, например, public void feedHippo(int animalCount) , или универсальный public void feedAnimal(Animal animal, int animalCount) ? На данном этапе возникнут проблемы. Решением может быть создание мапы,содержащей всех работников. Но тогда необходимо хранить ключи к ней (или хардкодить). Можно сделать ключом непосредственно значение перечисления, но все равно придется где-то собирать мапу. Другим решением может стать внедрение работников в качестве полей, но их [работников] может быть много, а feedAnimal будет опять работать на громоздком switch-case. И все эти варианты нужно поддерживать, а при добавлении нового животного придется искать по коду, где отрабатывает логика кормления.
Однако, если изменим перечисление следующим образом:
Все становится настолько простым:
Мы рассмотрели 3 варианта с нарастающей сложностью, где можно красиво применить перечисление вместо switch-case. Предложенные решения задач просты в реализации и более поддерживаемы и расширяемы по сравнению с решением «в лоб» с использованием switch-case.
Альтернатива Switch Case в Java
Есть ли альтернативный способ реализовать случай переключения в Java, отличный от if else, который не выглядит хорошим. Набор значений будет присутствовать в комбинации, в соответствии с выбранным соответствующим методом должен быть выполнен.
13 ответов
Предположительно, вы боретесь с тем, что требование является постоянным. Как правило, это запах кода, но есть вещи, которые вы можете сделать. Возможно, вы захотите поднять и связать с другим вопросом, который детализирует, почему вы пытаетесь переключиться.
В приведенном выше примере вам может понадобиться сопоставить «обработчики», что-то вроде
который затем превращает все это в поиск.
Опять же, это немного запах, поэтому, пожалуйста, напишите вопрос, который иллюстрирует рассуждения.
Если у вас есть много операторов switch/case вокруг вашего кода, и они сбивают вас с ума.
Вы можете выбрать Рефакторинг: Заменить условный полиморфизм.
Скажем, у вас есть часть программного обеспечения, которое используется для сохранения информации на разных устройствах: определены четыре операции сохранения: выборка, сохранение, удаление, обновление, которые могут быть реализованы N числом механизм сохранения (плоские файлы, сеть, СУБД, XML и т.д.).
Ваш код должен поддерживать их всех, поэтому в 4 разных местах у вас есть следующее:
перед
То же самое для сохранения/удаления/обновления
Использование оператора switch/case становится проблематичным:
Каждый раз, когда вы хотите добавить новый тип, вам нужно вставить новый ключ/регистр в каждый раздел.
Много раз некоторые типы схожи, и им не нужен другой ключ /case (вы можете их каскадировать)
Таким образом, рефакторинг должен состоять в том, чтобы добавить интерфейс или абстрактный тип, и различные типы реализуют этот интерфейс и делегируют ответственность за этот объект.
Итак, у вас будет что-то вроде этого:
после
И различные реализации
И другие типы будут реализовываться в соответствии с их логикой. Сеть будет иметь дело с сокетами, а потоки, БД будут иметь дело с JDBC, ResultSets и т.д. XML с node и т.д.
И, наконец, вам просто нужно делегировать вызовы.
Итак, вам просто нужно создать правильный экземпляр для менеджера сохранения в соответствии с типом только один раз. Затем все вызовы разрешаются полиморфизмом. Это одна из ключевых особенностей объектно-ориентированной технологии.
Если вы решите, что вам нужен другой менеджер персистентности, вы просто создаете новую реализацию и назначаете ее классу.
Alternative to Switch Case in Java
Is there any alternative way to implement a switch case in Java other than if else which is not looking good. A set of values will be there in combination, according to the selection corresponding method has to be executed.
14 Answers 14
If you have plenty of switch/case statements around your code and they are driving you crazy.
You could opt for the Refactoring: Replace conditional with polymorphism.
Let’s say you have a piece of software that is used to save information to different devices: 4 persistence operations are defined: fetch, save, delete, update, which could be implemented by N number of persistence mechanism ( flat files, network, RDBMS, XML, etc ) .
Your code have to support them all so in 4 different places you have this:
BEFORE
Same for save/delete/update
Using switch/case statement becomes problematic:
Each time you want to add a new type you have to insert new switch/case in each section.
Many times, some types are similar, and they don’t need a different switch/case ( you could cascade them )
Some other they are, and some times they differ slightly
You may even need to load different type at runtime ( like plugins )
So the refactoring here would be to add an interface or abstract type and have the different types implement that interface and delegate the responsibility to that object.
So you would have something like this:
AFTER
And different implementations
And the other types would implement according to their logic. Network would deal with sockets, and streams, DB would deal with JDBC, ResultSets etc. XML with node etc.etc.
And finally you just have to delegate the invocations.
So, you just have to create the correct instance for the persistence manager according to the type only once. Then all the invocations are resolved by polymorphism. That’s one of the key features of Object Oriented Technology.
If you decide you need another persistence manager, you just create the new implementation and assigned to the class.