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 нас полюбил, набросаем быстрый интерфейс. Каждый элемент этого массива — это объект с тремя полями:

  1. name — имя стратегии (чтобы мы понимали, что это за “предмет гардероба”).

  2. match — условие. Функция, которая отвечает на вопрос: «Эта ситуация подходит под мою стратегию?». В боевом коде это будет набор условий по различным входным данным, но мы не должны забывать про чистоту кода!

  3. 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. Никаких вложенных условий, только сортировка массива. А если ты за полный контроль и жёсткое доминирование — позаботься о сортировке!

Почему подготовленный «костюм» удобен в повседневной жизни?

  1. Open/Closed Principle (OCP). Нужно добавить новый флаг super_mega_error? Ты просто создаёшь новый объект и пушишь его в массив. Тебе вообще не нужно трогать основной код компонента и метод find. Код открыт для расширения, но закрыт для модификации.

  2. Легко тестить. Хочешь проверить, правильно ли работает warning в Unit-тестах? Просто возьми этот объект из массива, скорми ему фейковые options и замокай this.ui. Не нужно эмулировать весь компонент целиком.

  3. Переиспользуемость. Завтра тебе скажут сделать точно такую же валидацию, но для другого компонента? Ты просто импортируешь массив validationStrategies и используешь его там.

  4. Читаемость. Открыл файл, посмотрел на массив — сразу понял все возможные состояния UI и бизнес-логику. Это как посмотреть на человека и сразу понять, куда он собрался: на пляж или на деловую встречу.

Итог

Паттерны проектирования — это не скучная теория из учебников для заучивания на собеседованиях. Это реальные инструменты, которые экономят нервы. Стратегия помогает не потеряться в куче условий и соблюдает SOLID, а Фасад прячет всю UI-кухню под капотом, предоставляя чистый API.

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

А как вы обычно решаете проблему множественных состояний UI? Делитесь в комментариях, всегда интересно посмотреть на чужие архитектурные решения (и, чего греха таить, на элегантные костыли)!

ссылка на оригинал статьи https://habr.com/ru/articles/1087862/