Знаешь это чувство, когда открываешь свой шкаф и ничего не можешь найти, потому всё не на своих местах и нет никакой системы куда бы “подсунуть” новую покупку? Ты опаздываешь, нервничаешь, а вместо стильного образа на важную встречу собираешь какой-то винегрет из мятых вещей.

В программировании бывает точно так же. Только вместо шкафа у нас код, а вместо одежды -- куча условий 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? Делитесь в комментариях, всегда интересно посмотреть на чужие архитектурные решения (и, чего греха таить, на элегантные костыли)!

Комментарии (0)