TypeScript: Как подобрать элегантный “костюмчик” из гардероба вашему коду используя паттерны: Стратегия и Фасад

Знаешь это чувство, когда открываешь свой шкаф и ничего не можешь найти, потому всё не на своих местах и нет никакой системы куда бы “подсунуть” новую покупку? Ты опаздываешь, нервничаешь, а вместо стильного образа на важную встречу собираешь какой-то винегрет из мятых вещей.
В программировании бывает точно так же. Только вместо шкафа у нас код, а вместо одежды -- куча условий 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? Делитесь в комментариях, всегда интересно посмотреть на чужие архитектурные решения (и, чего греха таить, на элегантные костыли)!
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.