Знаешь это чувство, когда открываешь свой шкаф и ничего не можешь найти, потому всё не на своих местах и нет никакой системы куда бы “подсунуть” новую покупку? Ты опаздываешь, нервничаешь, а вместо стильного образа на важную встречу собираешь какой-то винегрет из мятых вещей.
В программировании бывает точно так же. Только вместо шкафа у нас код, а вместо одежды — куча условий if-else, нагроможденных друг на друга.
Допустим, ты пилишь UI-компонент формы ввода. И у тебя есть набор флагов состояния: empty, info, warning или того хуже, error.
Если писать «в лоб», код превращается в жуткого монстра, которого стыдно показать на код-ревью:
if (this.options.error) { this.ui.setBorder('blood-red'); this.ui.showTooltip('Всё пропало... Шеф!'); this.ui.blockInput();} else if (this.options.warning) { // ... ещё куча строк кода} else if (this.options.info) { // ... и ещё немного кода} else if (this.options.empty) { // ... и ещ] чуть-чуть}
Читать это через месяц — то ещё удовольствие. А если дизайнер скажет: «Слушай, а давай для info добавим ещё и синюю иконку?» Тебе придётся лезть в эту кашу, нарушая все традиции единственности (SRP / SOLID), и молиться, чтобы ничего не сломать.
Сегодня под катом разберём, как навести порядок в гардеробе и сшить коду идеальный «костюмчик», элегантно подружив два паттерна: Стратегию и Фасад. Спойлер: твой код станет чище, а жизнь — проще.
Стратегия: раскладываем вещи по полочкам
Стратегия — это как идеальная система хранения. У тебя есть отдельные боксы для галстуков, полки для рубашек и штанга для пиджаков. Ты не сваливаешь всё в одну кучу, а создаёшь набор отдельных, независимых сущностей.
Мы создаём массив validationStrategies. Чтобы TypeScript нас полюбил, набросаем быстрый интерфейс. Каждый элемент этого массива — это объект с тремя полями:
-
name— имя стратегии (чтобы мы понимали, что это за “предмет гардероба”). -
match— условие. Функция, которая отвечает на вопрос: «Эта ситуация подходит под мою стратегию?». В боевом коде это будет набор условий по различным входным данным, но мы не должны забывать про чистоту кода! -
apply— действие. Что конкретно мы делаем. Подбор упакованных костюмов для выхода чтобы мы не мешкали опаздывая на встречу.
interface ValidationOptions { value?: string; empty?: boolean; info?: boolean; warning?: boolean; error?: boolean;}interface ValidationStrategy { name: string; match: (options: ValidationOptions) => boolean; apply: (options: ValidationOptions) => void; // Тут прячется Фасад!}
Теперь посмотрим на примере наш массив (без фанатизма):
const validationStrategies: ValidationStrategy[] = [ { name: 'error', match: (options) => options.error === true, apply: (options) => { /* магия преображения */ } }, { name: 'warning', match: (options) => options.warning === true, apply: (options) => { /* ... */ } }, { name: 'info', match: (options) => options.info === true, apply: (options) => { /* ... */ } }, { name: 'empty', match: (options) => !options.value && options.empty, apply: (options) => { /* ... */ } }, // ... и т.п.];
Шкаф рассортирован. Каждая стратегия инкапсулирована (ООП) и знает только своё дело.
Фасад: прячем бирки, нитки и нижнее бельё
А теперь самое интересное. Что находится внутри функции apply? Там находится Фасад.
Фасад — это как идеально сидящий пиджак. Никто не видит, сколько раз ты обжегся утюгом, пока его гладил, какие там подплечники и сколько пуговиц пришлось перешить. Все видят только стильный результат.
В нашем случае apply — это и есть тот самый пиджак. За ним скрывается вся сложная UI/UX логика: изменение CSS-классов, отрисовка тултипов, переключение поля в режим ReadOnly или добавление кнопки-экшена.
Смотри, как это выглядит на практике:
{ name: 'warning', match: (options) => options.warning === true, apply: (options) => { // Фасад берет на себя всю рутину! this.ui.setBorderColor('yellow'); this.ui.showTooltip('Эй, проверь данные, тут что-то не так'); this.ui.setReadOnly(false); this.ui.hideActionButton(); }}
А теперь, попробуем добавить что-нибудь новенькое: trace — когда мы просто логируем всё для отладки нашего рещения, но не мешаем юзеру):
{ name: 'trace', match: (options) => options.trace === true, apply: (options) => { this.ui.setBorderColor('transparent'); this.ui.hideTooltip(); this.logger.trace('Поле прошло валидацию', options); }}
Мы спрятали всю грязную работу с DOM-элементами и стилями за одним понятным контрактом. Снаружи — чистота и элегантность.
Примерка! Или как довести процесс до автоматизма?!
Окей, массив есть, стратегии написаны, фасады спрятаны. Как заставить эту машину ехать самой? Всё гениальное просто. Мы берём текущие абстрактные параметры (this.options), которые прилетели в наш класс-потомок без конкретной типизации, а может и с ней для частного случая и ищем первую стратегию, которая скажет: «О, боже! Это именно то что мне сейчас нужно!». И зовёт сама на 2-е свидание и все прочие опции…
// Ищем подходящую стратегиюconst activeStrategy = this.validationStrategies.find(s => s.match(this.options));// Если нашли — "надеваем" еёif (activeStrategy) { activeStrategy.apply(this.options);}
Коротеннечно, правда? Вместо 100500 строк if-else — три строчки простого и понятного кода.
⚠️ Важный нюанс (Ловушка для новичков — Не переборщи с парфюмом! И не забудь заправить рубашку в брюки!)
Метод массива find ищет первый подходящий элемент. Это значит, что порядок объектов в validationStrategies критически важен!
Если у тебя одновременно прилетели флаги warning: true и error: true, сработает та стратегия, которая лежит в массиве выше. Поэтому всегда ставь самые критичные состояния (error, warning) в самое начало массива, а фоновые (info, empty) — в конец. Ты сам управляешь приоритетами, просто перетаскивая объекты мышкой в IDE. Никаких вложенных условий, только сортировка массива. А если ты за полный контроль и жёсткое доминирование — позаботься о сортировке!
Почему подготовленный «костюм» удобен в повседневной жизни?
-
Open/Closed Principle (OCP). Нужно добавить новый флаг
super_mega_error? Ты просто создаёшь новый объект и пушишь его в массив. Тебе вообще не нужно трогать основной код компонента и методfind. Код открыт для расширения, но закрыт для модификации. -
Легко тестить. Хочешь проверить, правильно ли работает
warningв Unit-тестах? Просто возьми этот объект из массива, скорми ему фейковыеoptionsи замокайthis.ui. Не нужно эмулировать весь компонент целиком. -
Переиспользуемость. Завтра тебе скажут сделать точно такую же валидацию, но для другого компонента? Ты просто импортируешь массив
validationStrategiesи используешь его там. -
Читаемость. Открыл файл, посмотрел на массив — сразу понял все возможные состояния UI и бизнес-логику. Это как посмотреть на человека и сразу понять, куда он собрался: на пляж или на деловую встречу.
Итог
Паттерны проектирования — это не скучная теория из учебников для заучивания на собеседованиях. Это реальные инструменты, которые экономят нервы. Стратегия помогает не потеряться в куче условий и соблюдает SOLID, а Фасад прячет всю UI-кухню под капотом, предоставляя чистый API.
Так что в следующий раз, когда откроешь свой «шкаф» и ужаснёшься от бардака — просто вспомни про карточки стратегий. И подбери своему коду костюмчик по фигуре и месту встречи. 😉
А как вы обычно решаете проблему множественных состояний UI? Делитесь в комментариях, всегда интересно посмотреть на чужие архитектурные решения (и, чего греха таить, на элегантные костыли)!
ссылка на оригинал статьи https://habr.com/ru/articles/1087862/