Как стать автором
Обновить
175.92
Beget
Beget — международный облачный провайдер

Программирование без условных операторов

Время на прочтение5 мин
Количество просмотров4.8K


В программировании на C-подобных языках (и не только) частенько приходится использовать условные операторы, такие как If, else, switch — и особенно часто их используют новички, потому что их понимание и работа достаточно проста (в итоге, могут рождаться, иной раз, поистине монструозные конструкции — грешен, практиковал:-))).

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

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

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

Используем полиморфизм


В достижении нашей задачи одним из путей может быть использование полиморфизма, который, если простыми словами, представляет собой возможность объектов с одинаковой специализацией, выполнять действия разными способами: например, возьмём кнопки на пульте дистанционного управления — где одни могут регулировать громкость, другие могут переключать канал, третьи — ставить на паузу…

В случае Java это выглядит таким образом, что создаётся общий интерфейс или абстрактный класс, а разные классы реализуют этот интерфейс по-разному.

Полиморфизм придаёт коду гибкость и расширяемость, упрощая добавление новых вариантов поведения и уменьшая потребность в условных операторах.

Допустим, в старом варианте, с использованием условных операторов это выглядело бы так:



В новом же варианте, мы можем определить интерфейс, после чего написать реализацию его для каждого типа источника:



Далее, выбор конкретной реализации может быть передан «фабрике» — что избавляет нас от необходимости реализации проверок в основном коде.

Фабрика — это такой специальный объект, который предназначен для создания других объектов.

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

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



Однако, в варианте без условных операторов, эта фабрика будет выглядеть так:



Что мы здесь видим: существует карта Map, заполненная ключами (например, source_1) и значениями, где в качестве значений выступают конструкторы соответствующих классов.

Например, при обращении к ключу source_1 — происходит вызов конструктора класса SensorOneProcessor, который и создаст новый экземпляр класса.

Но, это только список конструкторов (кстати говоря, такой подход очень удобен тем, что можно легко добавлять новые классы со своими реализациями — нужно только дополнить Map).

Для того, чтобы непосредственно создать экземпляр класса, ниже, под Map мы видим фабричный метод createProcessor(String type), который на вход принимает значение ключа (по которому надо искать конструктор в Map), и если мы ей передадим соответствующий ключ, — она создаст нужный нам экземпляр класса, найдя его в Map.

Выглядит это следующим образом — сначала, чтобы воспользоваться всей этой конструкцией фабрики, нам надо инициализировать фабрику:



Далее, мы у созданной фабрики вызываем метод createProcessor, одновременно передавая ключ, для создания нужного экземпляра класса:



Вот и всё! Чем этот подход лучше, по сравнению с использованием условных операторов: все изменения локализованы в одном месте, код клиента не знает о конкретных классах, и вся эта конструкция легко тестируется. Это не единственно возможный подход, можно то же самое сделать и через абстрактные классы…

Применение функционального программирования


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

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

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

Такой подход может быть полезен при обработке коллекций, в асинхронных задачах, в конвейерах данных; также, он может быть применён и при замене сложных условий.

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



Как можно видеть, в случае функционального подхода используется контейнерный класс Optional, появившийся в Java 8, и предназначенный для хранения значений, у которого мы вызываем фабричный метод ofNullable, осуществляющий проверку переданного объекта на null – где, в случае не нулевого значения, создаётся экземпляр класса с этим значением, тогда как в случае null — создаётся пустой экземпляр класса.

Проще говоря, это можно сравнить с кладовщиком, который смотрит, есть ли посылка? Если посылка есть — он кладёт её в коробку; если посылки нет — достаёт пустую коробку.

Также там далее можно видеть цепочку вызовов методов (начинаются с точки спереди — .filter, .map), что можно прочитать как: взять заказ, отфильтровать валидные значения, преобразовать в результат или вернуть ответ «Invalid data».

Использование шаблона «спецификация»


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

Смысл заключается в том, что в дальнейшем, можно производить комбинирование этой логики разными способами (А и Б, Б и С, С и А и т.д.).

Выглядеть это может, например, следующим образом:



В случае выше, это работает следующим образом:
  • создаём интерфейс, в котором boolean-метод содержит логику, которая проверяет данные;
  • добавляем логическое «И» (and() ), которое будет комбинировать два условия;
  • реализуем конкретные правила в отдельных классах, где каждый класс проверяет своё условие, например, CurrentDayData — является ли дата сегодняшней, первый ли это день месяца и превышает ли число данных 3000;
  • далее, в классе-обработчике (DataProcessor) пишем комбинированное правило (type), которое будет проверять два условия: являются ли данные – данными сегодняшнего дня и срочными;
  • после чего проверяем это комбинированное правило, там же, в обработчике: type.isCorresponds(data) и если данные соответствуют условиям, то осуществляем ускоренную обработку, если не соответствуют — стандартную.

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

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

Иногда хороший код — это просто тот код, который легко написать, легко поддерживать, и он сможет начать работать за минимальное время, вот и всё.

P.S. Напоследок скажу, что шаблон «спецификация» — не единственный, который может заменить условные операторы, есть и ещё, как минимум, один, вариант. Какой, на ваш взгляд?;-) И к слову — как вы поступаете, чтобы уйти от ef-else?
Теги:
Хабы:
+7
Комментарии29

Публикации

Информация

Сайт
beget.com
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия