Инверсия контроля — улучшите свои абстракции


Какую проблему она решает?

Формулировка проблемы часто более важна, чем ее решение

Если вы пишете код в течение некоторого времени, я уверен, что вы написали функциональные возможности, которые используются более чем в одном месте. Это может быть:

  • Утилитарная функция, выполняющая определенную задачу.
  • Компонент React, который вы повторно использовали в нескольких местах.
  • Компонент-обертка, который инкапсулирует некоторую функциональность.

Что бы это ни было, вполне вероятно, что, пытаясь написать хороший код, вы столкнулись со следующей моделью проблем:

  1. Вы создаете многократно используемую функциональность.
  2. Возникает какой-то сценарий использования, который ваш многоразовый код не поддерживает.
  3. Вы добавляете в код опции (через аргументы / реквизиты) и собираете вместе цепочки операторов if-else.
  4. В результате файл продолжает увеличиваться, а абстракция кажется скорее бременем, чем полезностью.

Весь этот сценарий создает две основные проблемы:

1. Ненужные накладные расходы на реализацию: Все эти ветвящиеся утверждения if-else делают компонент более сложным для тестирования и снижают читаемость кода. Кроме того, каждый новый блок if-else усугубляет существующую сложность кода.

2.Сложные накладные расходы на api: С каждым новым реквизитом/аргументом возникает необходимость обновления документации для конечных пользователей (например, для вас в будущем). Также бывают случаи, когда специфическая функциональность, необходимая конечному пользователю, отсутствует в коде. В таких случаях разработчику, возможно, придется искать в бесконечном море документации, чтобы проверить, поддерживается ли их вариант использования.

Решение — инверсия контроля 🔥.

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

Хватит разговоров, давайте кодить!

В этом упражнении мы будем реализовывать простую функцию сортировки! Первоначальная реализация будет наивной абстракцией. Затем мы рефакторим ее и реализуем принцип инверсии управления.

const sortArray = (array) => array.sort();
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь этой общей функции сортировки недостаточно для всех случаев использования, поскольку она сортирует только в алфавитном порядке. Давайте добавим возможность указать порядок сортировки — по возрастанию или по убыванию!

const sortArray = (array, order="ascending") => {
  if(order === "ascending")
    return array.sort((a,b) => a - b);
  else if(order === "descending")
    return array.sort((a,b) => b - a);
  else
   throw new Error("sorting order not valid")
}

// logs [100,2,2,1,1]
console.log(sortArray([1,2,100,2,1], order="descending"));

Вход в полноэкранный режим Выход из полноэкранного режима

И все работает. Через несколько дней появляется еще одно требование: Поддержка массивов объектов. Сортировка их по ключам.

Чтобы поддержать это новое требование, мы снова добавляем новый флаг, sortObject, и реализуем нашу логику.

const sortArray = (array, sortObject, key, order="ascending") => {
  if(sortObject) {
    if(order === "ascending")
      return array.sort((a,b) => a[key] - b[key]);
    else if(order === "descending")
      return array.sort((a,b) => b[key] - a[key]);
    else
     throw new Error("sorting order not valid")
  } else {
    if(order === "ascending")
      return array.sort((a,b) => a - b);
    else if(order === "descending")
      return array.sort((a,b) => b - a);
    else
      throw new Error("sorting order not valid")
  }

}

//logs [{key: 2, name: "B"}, {key: 1, name: "A"}]
console.log(sortArray([{key: 1, name: "A"}, {key: 2, name: "B"}], true, "key", order="descending"));
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы можете видеть, каждое новое требование усугубляет существующую кучу кода, создавая тем самым ад «if-else». Требования будут продолжать расти и в будущем. Так есть ли у нас выход? Конечно, есть!

Давайте проведем рефакторинг по принципу IOC.

Мы оставим «как будет происходить сортировка» на усмотрение разработчика. Наша задача — просто объявить абстракцию, направить логический поток и вернуть желаемый результат. Таким образом, код станет примерно таким:

const sortArray = (array, sortFn) => {
  if (sortFn) {
    return array.sort(sortFn);
  }
  return array.sort();
}

// logs [4,3,2,1]
console.log(sortArray([1,2,3,4], sortFn = (a,b) => b - a))

//logs [{key: 2, name: "B"}, {key: 1, name: "A"}]
console.log(sortArray([{key: 1, name: "A"}, {key: 2, name: "B"}], sortFn = (a,b) => b["key"] - a["key"]));
Войти в полноэкранный режим Выйти из полноэкранного режима

И с этим вы можете увидеть разницу! Наш код является модульным, и мы передали ответственность за определение функции сортировки конечному пользователю. Тестировать этот новый кусок кода стало намного проще, чем раньше!

Вам кажется, что вы уже видели этот паттерн раньше?

Конечно, да. Многие распространенные встроенные функции, такие как map, filter, reduce, sort, forEach, реализуют этот принцип проектирования!

Не только это, React hooks, state-reducer pattern также основан на IOC. Многие веб-фреймворки основаны на этом принципе. Dependency Injection, распространенный и важный паттерн проектирования, также основан на принципе IOC.

Заключение

Как и любой другой инструмент, мы должны понимать необходимость внедрения IOC. Если мы имеем представление об условиях и ветвлениях в нашей логике, мы можем реализовать неинвертированные абстракции. С другой стороны, если мы имеем дело с неизвестным количеством расширений функциональности, инвертирование управления может быть идеальным выбором.

Дополнительный бит!

Во второй части этой статьи мы расскажем, как использовать инверсию управления для реализации защищенных маршрутов на основе ролей с помощью react и react-router-dom v6. Оставайтесь с нами!

Примечания:

Эта статья в значительной степени вдохновлена статьей Кента Доддса (Kent C. Dodds) о том же. Вот ссылка на статью.

Оцените статью
devanswers.ru
Добавить комментарий