Был у меня код вроде вот такого ([[nodiscard]] и прочие элементы хорошего стиля убраны для упрощения):
|
class element_finder_t { public: search_result_t find_element(const search_conditions_t & conditions) { if(should_use_first_algorithm(conditions)) return find_by_first_algorithm(conditions); else if(should_use_second_algorithm(conditions)) return find_by_second_algorithm(conditions); ... } private: search_result_t find_by_first_algorithm(const search_conditions_t & conditions) { ... // Какой-то код для поиска элемента в цикле. if(is_conditions_fulfilled(element_info, conditions)) return element_info; ... // Какой-то код для подготовки следующей итерации. } search_result_t find_by_second_algorithm(const search_conditions_t & conditions) { ... // Какой-то код для поиска элемента в цикле. if(is_conditions_fulfilled(element_info, conditions)) return element_info; ... // Какой-то код для подготовки следующей итерации. } }; |
В один прекрасный день потребовалось кроме метода find_element добавить еще и метод find_element_with_postprocessing. Отличие нового метода от старого find_element в том, что когда элемент удовлетворяющий условиям поиска найден, то нужно сделать некую дополнительную постобработку. К сожалению, постобработка может привести к тому, что элемент перестанет удовлетворять условиям поиска. В таком случае нужно попытаться найти следующий подходящий элемент.
В лоб такое расширение element_finder_t можно было бы сделать так:
|
class element_finder_t { public: search_result_t find_element(const search_conditions_t & conditions) { if(should_use_first_algorithm(conditions)) return find_by_first_algorithm(conditions); else if(should_use_second_algorithm(conditions)) return find_by_second_algorithm(conditions); ... } // Новый метод, фактически повторяет логику старого. search_result_t find_element_with_postprocessing( const search_conditions_t & conditions, const processing_params_t & params) { if(should_use_first_algorithm(conditions)) return find_by_first_algorithm_with_postprocessing( conditions, params); else if(should_use_second_algorithmconditions)) return find_by_second_algorithm_with_postprocessing( conditions, params); ... } private: search_result_t find_by_first_algorithm(const search_conditions_t & conditions) { ... // Какой-то код для поиска элемента в цикле. if(is_conditions_fulfilled(element_info, conditions)) return element_info; ... // Какой-то код для подготовки следующей итерации. } search_result_t find_by_second_algorithm(const search_conditions_t & conditions) { ... // Какой-то код для поиска элемента в цикле. if(is_conditions_fulfilled(element_info, conditions)) return element_info; ... // Какой-то код для подготовки следующей итерации. } search_result_t find_by_first_algorithm_with_postprocessing( const search_conditions_t & conditions, const processing_params_t & params) { ... // Какой-то код для поиска элемента в цикле. if(is_conditions_fulfilled(element_info, conditions)) { auto updated_info = postprocess(element_info, params); if(is_conditions_fulfilled(updated_info, conditions)) return updated_info; } ... // Какой-то код для подготовки следующей итерации. } search_result_t find_by_second_algorithm_with_postprocessing( const search_conditions_t & conditions, const processing_params_t & params) { ... // Какой-то код для поиска элемента в цикле. if(is_conditions_fulfilled(element_info, conditions)) { auto updated_info = postprocess(element_info, params); if(is_conditions_fulfilled(updated_info, conditions)) return updated_info; } ... // Какой-то код для подготовки следующей итерации. } }; |
Т.е. приходится дублировать кучу кода из-за нескольких различающихся строчек где-то в глубине конкретных алгоритмов поиска. Что есть копипаста, а от копипасты следует избавляться.
Как?
С помощью шаблонов, конечно же! На самом деле можно и без шаблонов, но тема-то "шаблоны против копипасты" 😉
Поэтому делаем так:
|
class element_finder_t { // Вспомогательный тип для случая, когда пост-процессинг делать не нужно. struct no_processing_params_t {}; public: search_result_t find_element(const search_conditions_t & conditions) { // Делегируем саму работу. return do_find_element(conditions, no_processing_params_t{}); } // Новый метод. search_result_t find_element_with_postprocessing( const search_conditions_t & conditions, const processing_params_t & params) { // Делегируем саму работу туда же. return do_find_element(conditions, params); } private: // Вот кто реально делает работу. template<typename OptProcessingParams> search_result_t do_find_element( const search_conditions_t & conditions, const OptProcessingParams & opt_processing_params) { if(should_use_first_algorithm(conditions)) return find_by_first_algorithm( conditions, opt_processing_params); else if(should_use_second_algorithm(conditions)) return find_by_second_algorithm( conditions, opt_processing_params); ... } // Методы для разных способов поиска стали шаблонами. template<typename OptProcessingParams> search_result_t find_by_first_algorithm( const search_conditions_t & conditions, const OptProcessingParams & opt_processing_params) { ... // Какой-то код для поиска элемента в цикле. if(is_conditions_fulfilled(element_info, conditions)) { // Обработка ситуации с пост-процессингом. if constexpr(std::is_same_v<OptProcessingParams, processing_params_t>) { auto updated_info = postprocess(element_info, params); if(is_conditions_fulfilled(updated_info, conditions)) return updated_info; } else return element_info; } ... // Какой-то код для подготовки следующей итерации. } template<typename OptProcessingParams> search_result_t find_by_second_algorithm( const search_conditions_t & conditions, const processing_params_t & opt_processing_params) { ... // Какой-то код для поиска элемента в цикле. if(is_conditions_fulfilled(element_info, conditions)) { // Обработка ситуации с пост-процессингом. if constexpr(std::is_same_v<OptProcessingParams, processing_params_t>) { auto updated_info = postprocess(element_info, params); if(is_conditions_fulfilled(updated_info, conditions)) return updated_info; } else return element_info; } ... // Какой-то код для подготовки следующей итерации. } }; |
Т.е. всю работу вынесли в минимальное количество вспомогательных шаблонных методов. И фокус в том, что если пост-процессинг нужен, то во вспомогательные методы отдаются реальные параметры для него. Этот случай специально обрабатывается в конкретных алгоритмах через if constexpr.
Если же постпроцессинг не нужен, то передается ссылка на пустое значение no_processing_params_t и никаких действий по пост-процессингу не выполняется.
Тем самым удалось избежать дублирования основного кода алгоритмов поиска лишь немного усложнив их за счет дополнительного if constexpr.
Комментариев нет:
Отправить комментарий