Что значит инвертированное управление

Инверсия управления/Inversion of Control

Инверсия управления является распространенным явлением, с которым вы столкнетесь при использовании фреймворков. И действительно, она часто рассматривается как определяющая характеристика фреймворка.

Давайте рассмотрим простой пример. Представьте себе, что я пишу программу, которая получает некоторую информацию от пользователя с помощью командной строки. Я мог бы сделать это как-то так:

В этой ситуации мой код управляет исполнением: он решает, когда задавать вопросы, когда считывать ответы, а когда обрабатывать результаты.

Однако если бы я использовал оконную систему для чего-то похожего, я написал бы что-то, что работает с окном:

Теперь между этими двумя программами большая разница в потоке управления — в частности, в управлении временем, когда вызываются методы process_name и process_quest. В примере с коммандной строкой я контролирую, когда эти методы вызываются, но в примере с оконным приложением нет. Вместо этого я передаю контроль оконной системе (команда Tk.mainloop). Далее она решает, когда вызвать мои методы, основываясь на связях, которые я настроил при создании формы. Управление инвертировано — управляют мной, а не я управляю фреймворком. Это явление и называется инверсией управления (также известно как Принцип Голливуда — «Не звони нам, мы сами позвоним тебе» — Hollywood Principle — «Don’t call us, we’ll call you»).

Одной важной характеристикой фреймворка является то, что методы, определенные пользователем для адаптации фреймворка под свои нужды, будут чаще всего вызываться внутри самого же фреймворка, а не из кода приложения пользователя. Фреймворк часто играет роль главной программы в координации и последовательности действий приложения. Такая инверсия управления дает фреймворку возможность служить расширяемым скелетом приложения. Методы, предоставляемые пользователем, адаптируют общие алгоритмы, определенные фреймворком, под определенное приложение.

Ральф Джонсон и Брайан Фут.

Читайте также:  Тема сказки что это значит

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

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

Существуют различные способы подключить ваш код для его дальнейшего вызова. В предыдущем примере на ruby, мы вызываем метод bind текстового поля, который принимает имя события и замыкание в качестве аргументов. Всякий раз, когда текстовое поле узнает о событии, оно вызывает наш код из замыкания. Использование таких замыканий очень удобно, но многие языки не поддерживают их.

Еще один способ сделать это — сделать так, чтобы фреймворк определил события, а код пользователя подписался на эти события. .NET является хорошим примером платформы, которая позволяет людям объявлять свои события на виджеты средствами языка. Вы можете назначить метод для события с помощью делегатов.

Рассмотренные выше подходы (они одинаковые) хорошо подходят в единичных случаях, но иногда вам может понадобиться объединить некоторое количество вызовов в единый блок расширения. В этом случае фреймворк может определить интерфейс, который ваш код должен будет реализовать для соответствующих вызовов.

EJB-компоненты являются хорошим примером такого стиля инверсии управления. При разработке сессионного компонента(session bean), вы можете реализовать различные методы, которые вызываются EJB-контейнером в различных точках/состояниях жизненного цикла. Например, у интерфейса SessionBean есть методы ejbRemove, ejbPassivate (сохранен во вторичное хранилище) и ejbActivate (восстановлен из пассивного состояния). Вы не можете управлять вызовом этих методов, только тем, что они делают. Контейнер вызывает нас, а не мы вызываем его.

Примечание перевода, пример:

Это сложные случаи инверсии управления, но вы столкнетесь с этим в гораздо более простых ситуациях. Шаблонный метод является хорошим примером: супер-класс определяет поток управления, субклассы наследуются от него переопределяя методы или реализуя абстрактные методы. Например, в JUnit, код фреймворка вызывает методы setUp и tearDown для вас, чтобы создавать и очищать ваш тест. Происходит вызов, ваш код реагирует — это снова инверсия управления.

Примечание перевода, пример:

В наши дни, в связи с ростом количества IoC-контейнеров, существует некоторая путаница со смыслом инверсии управления. Некоторые люди путают общий принцип с конкретными стилями инверсии управления (такими как внедрение зависимостей), которые эти контейнеры используют. Все это немного запутанно (и иронично), так как IoC-контейнеры, как правило, рассматриваются в качестве конкурента EJB, но EJB использует инверсию управления.

Этимология: Насколько я могу судить, термин инверсии управления впервые появился на свет в работе Джонсона и Фута Designing Reusable Classes, опубликованной в журнале Object-Oriented Programming в 1988 году. Работа является одной из тех, что в возрасте хороши — ее можно прочитать даже спустя пятнадцать лет. Они считают, что они взяли этот термин откуда-то еще, но не могут вспомнить откуда. Затем термин втерся в объектно-ориентированное сообщество и вновь появился в книге Gang of Four. Более красивый синоним «Принцип Голливуда», кажется, берет начало в работе Ричарда Свита в Mesa в 1983 году. В списке целей разработки он пишет: Не звони нам, мы сами позвоним тебе (Закон Голливуда): инструмент должен организовать Тахо, чтобы предупредить его, когда пользователь захочет передать какое-то событие инструменту, вместо того, чтобы принимать модель «запросить у пользователя команду и выполнить ее». Джон Влиссидес пишет колонку о C++, которая несет в себе хорошее объяснение концепции под названием «Принцип Голливуда». (Спасибо Брайану Футу и Ральфу Джонсону за помощь с этимологией).

Источник

Как использовать Инверсию Управления в JavaScript и в Reactjs для упрощения работы с кодом

Инверсия Управления это довольно простой для понимания принцип программирования, который, при этом, может заметно улучшить ваш код. В данной статье будет показано как применять Инверсию Управления в JavaScript и в Reactjs.

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

  1. Вы создаете многократно используемый фрагмент кода (это может быть функция, React компонент, React хук и тд) и делитесь им (для совместной работы или публикуя в опенсорс).
  2. Кто-то обращается к вам с просьбой добавить новый функционал. Ваш код не поддерживает предложенный функционал, но мог бы, если бы вы сделали небольшое изменение.
  3. Вы добавляете новый аргумент/проп/опцию в свой код и в связанную с ним логику для поддержания работы этого нового функционала.
  4. Повторите стадии 2 и 3 несколько раз (или много-много раз).
  5. Теперь ваш повторно используемый код тяжело использовать и поддерживать.

Что именно делает код кошмаром для использования и поддержки? Есть несколько аспектов, которые могут сделать ваш код проблемным:

  1. Размер пакета и/или производительность: Просто больше кода для запуска на устройствах может привести к ухудшению производительности. Иногда это может привести к тому что люди просто откажутся использовать ваш код.
  2. Сложно поддерживать: Раньше у вашего повторно используемого кода было всего несколько опций, и он был сфокусирован на том, чтобы хорошо делать одну вещь, но теперь он может делать кучу разных вещей, и вам нужно это все документировать. Кроме того, люди начнут задавать вам вопросы о том, как использовать ваш код для тех или иных вариантов использования, которые могут, а может и нет, быть сопоставимы с вариантами использования, для которых вы уже добавили поддержку. У вас может быть даже два почти одинаковых варианта использования, которые немного отличаются, так что вам придется отвечать на вопросы о том что лучше использовать в той или иной ситуации.
  3. Сложность реализации: Каждый раз это не просто еще один оператор if , каждая ветка логики вашего кода сосуществует с уже имеющимися ветками логики. Фактически возможны ситуации когда вы пытаетесь поддерживать комбинацию аргументов/опций/пропсов, которую никто даже не использует, но вам все равно нужно учитывать любые возможные варианты, так как вы точно не знаете, использует или будет ли использовать кто либо эти комбинации.
  4. Сложный API: Каждый новый аргумент/опция/проп, который вы добавляете в свой повторно используемый код, затрудняет его использование, так как теперь у вас есть огромный README или сайт, где задокументирован весь доступный функционал, и людям приходиться изучать все это для эффективного использования вашего кода. Использовать его то же не удобно, потому что сложность вашего API проникает в код разработчика, который использует его, что усложняет и его код.

В итоге все страдают. Стоит заметить что реализация конечной программы является важнейшей частью разработки. Но было бы отлично если бы мы больше думали о реализации наших абстракций (читайте про «AHA programming»). Существует ли способ, который позволит нам уменьшить проблемы с повторно используемым кодом, и, при этом, все еще пожинать преимущества использования абстракций?

Инверсия управления

Инверсия управления это принцип который действительно упрощает создание и использование абстракций. Вот что говорит об этом Википедия:

… в традиционном программировании пользовательский код, который выражает назначение программы, вызывает в многократно используемых библиотеках для решения общих задач, но с инверсией управления это среда, которая вызывает пользовательский или специфичный для задачи код,

Думайте об этом так: «Сократите функционал вашей абстракции, и сделайте так чтобы ваши пользователи сами могли реализовывать нужный им функционал». Это может показаться полным абсурдом, мы же ведь для того и используем абстракции чтобы спрятать сложные и повторяющиеся задачи, и тем самым сделать наш код более «чистым» и «аккуратным». Но, как мы уже убедились выше, традиционные абстракции не всегда упрощают код.

Что такое Инверсия Управления в коде?

Для начала, вот очень надуманный пример:

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

Итак, наша программа работает всего с шестью вариантами использования, но фактически мы поддерживаем любую возможную комбинацию функций, и таких комбинаций насчитывается аж 25 (если я правильно посчитал).

В целом, это довольно простая абстракция. Но ее можно упростить. Часто бывает так, что абстракцию, в которую добавляли новый функционал, можно было бы сильно упростить для тех вариантов использования которые она фактически и поддерживает. К сожалению, как только абстракция начинает что-то поддерживать (например, выполнение < filterZero: true, filterUndefined: false >), мы боимся удалять эту функциональность из-за того что это может сломать код который на нее полагается.

Мы даже пишем тесты для вариантов использования, которых у нас, на самом деле нету, просто потому что наша абстракция поддерживает эти сценарии, и нам «может» понадобиться сделать это в будущем. А когда те или иные варианты использования становятся не нужными для нас, мы не удаляем их поддержку, так как просто забываем об этом, или думаем что это может пригодиться нам в будущем, или просто боимся чего нибудь сломать.

Окей, давайте теперь напишем более продуманную абстракцию к этой функции и применим метод инверсии управления для поддержки всех нужных нам вариантов использования:

Отлично! Вышло намного проще. Мы только что перевернули управление функцией, передав ответственность за принятие решения о том, какой элемент попадает в новый массив, с функции filter на функцию, вызывающую функцию фильтра. Заметьте, что функция filter все еще является полезной абстракцией сама по себе, но теперь она гораздо более гибкая.

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

Только представьте если бы вам нужно было добавить поддержку для этого варианта использования, не применяя инверсию управления? Да это было бы просто нелепо.

Плохой API?

Одна из самых распространенных жалоб, которую я слышу от людей, касательно API-интерфейсов в которых применяется инверсия управления это: «Да, но теперь этим сложнее пользоваться, чем раньше». Возьмите этот пример:

Да, один из вариантов явно проще в использовании, чем другой. Но одним из преимуществ инверсии управления является то что вы можете использовать API в котором применяется инверсия управления для повторной реализации вашего старого API. Обычно это довольно просто. К примеру:

Круто, да? Таким образом мы можем создавать абстракции поверх API в котором применяется инверсия управления, и тем самым создавать более простой API. А если в нашем «более простом» API недостаточно вариантов использования, тогда наши пользователи могут применить те же самые строительные блоки, которые мы использовали для создания нашего высокоуровневого API, чтобы разрабатывать решения для более сложных задач. Им не нужно просить нас добавить новую функцию в filterWithOptions и ждать, пока она будет реализована. У них уже есть инструменты при помощи которых они могут самостоятельно разрабатывать нужный им дополнительный функционал.

И, просто для фана:

Можно создавать специальный функционал для любой ситуации которая часто встречается у вас.

Примеры из реальной жизни

Итак, это работает в простых случаях, но подойдет ли эта концепция для реальной жизни? Что ж, скорее всего вы постоянно используете инверсию управления. К примеру, функция Array.prototype.filter применяет инверсию управления. Как и функция Array.prototype.map .

Существуют различные паттерны, с которыми вы, возможно, уже знакомы, и которые являются просто одной из форм инверсии управления.

Вот два моих любимых паттерна, которые демонструют это «Compound Components» и «State Reducers». Ниже идут краткие примеры того, как можно применять эти паттерны.

Составные Компоненты (Compound Components)

Допустим, вы хотите создать компонент Menu , который имеет кнопку для открытия меню и список пунктов меню, которые будут отображаться при нажатии на кнопку. Затем, когда элемент выбран, он будет выполнять какие-то действия. Обычно, чтобы реализовать это, просто создают пропсы:

Это позволяет нам много чего настроить в пунктах меню. Но что если мы хотим вставить линию перед пунктом меню Delete? Должны ли мы добавить специальную опцию к объектам относящимся к пропсу items ? Ну, я не знаю, к примеру: precedeWithLine ? Так себе идея.

Может, создать особый вид пункта меню, к примеру . Думаю, это сработало бы, но тогда нам пришлось бы обрабатывать случаи, когда нет onSelect . И, если честно, это очень неловкий API.

Когда вы думаете о том, как создать хороший API для людей, которые пытаются сделать что-то немного по-другому, вместо того чтобы тянуться к if оператору, попробуйте инвертировать управление. Что если мы передадим ответственность за визуализацию меню на пользователя? Используем одну из самых сильных сторон Реакта:

Важный момент, на который следует обратить внимание — здесь нет состояния ( state ) видимого пользователю компонентов. Состояние неявно разделяется между этими компонентами. Это основная ценность паттерна составных компонентов. Используя эту возможность, мы предоставили некоторый контроль над рендерингом пользователю наших компонентов, и теперь добавление дополнительной строки (или чего-то еще) является простым и интуитивно понятным действием. Никакой дополнительной документации, никаких дополнительных функций, никакого лишнего кода или тестов. Все в выигрыше.

Вы можете почитать больше об этом паттерне здесь. Спасибо Райану Флоренсу, который научил меня этому.

Редюсер Состояния (State Reducer)

Я придумал этот паттерн чтобы решить проблему настройки логики компонентов. Вы можете прочитать больше о той ситуации в моем блоге «The State Reducer Pattern», но основная суть в том, что у меня есть библиотека поиска/автозаполнения/ввода типов, которая называется Downshift , и один из пользователей библиотеки разрабатывал версию компонента с множественным выбором, из-за чего он хотел чтобы меню оставалось открытым даже после выбора элемента.

Логика Downshift предполагала что после того как выбор был сделан, меню должно закрыться. Пользователь библиотеки, которому нужно было изменить ее функционал, предложил добавить проп closeOnSelection . Я отказался от этого предложения, так как однажды уже прошел по пути ведущему к апропкалипсису, и хотел избежать этого.

Вместо этого я сделал API таким, чтобы пользователи сами могли контролировать как происходят изменения состояния. Думайте о состоянии редюсера ( state reducer ) как о состояний функции, которая вызывается каждый раз, когда изменяется состояние компонента, и дает разработчику приложения возможность влиять на изменение состояния, которое должно произойти.

Пример использования библиотеки Downshift таким образом чтобы она не закрывала меню после того как пользователь нажал на выбранный элемент:

После того как мы добавили этот проп, мы стали получать НАМНОГО меньше запросов на добавление новых настроек для этого компонента. Компонент стал более гибким, и разработчикам стало проще настраивать его так как нужно именно им.

Рендер-пропсы (Render Props)

Стоит упомянуть паттерн «render props». Этот паттерн является идеальным примером использования инверсии управления, но он нам особо больше и не нужен. Подробнее об этом здесь: why we don’t need Render Props as much anymore.

Предупреждение

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

Вернемся к нашему надуманному примеру:

Что если это все что нам требуется от функции filter ? И мы никогда не сталкивались с ситуацией, когда нам нужно было бы фильтровать что-либо кроме null и undefined ? В этом случае добавление инверсии управления для единственного варианта использования просто усложнило бы код и не принесло бы особой пользы.

Как и в случае с любыми абстракциями, будьте внимательны, применяйте принцип AHA Programming и избегайте поспешных абстракций!

Выводы

Надеюсь что статья была полезной для вас. Я показал как можно применять концепцию Инверсии Управления в Реакте. Эта концепция, конечно же, применима не только к React (как мы видели на примере функции filter ). В следующий раз когда вы заметите что добавляете очередной оператор if в функцию coreBusinessLogic вашего приложения, подумайте, как вы можете инвертировать управление и перенести логику туда, где она используется (или, если она используется в нескольких местах, вы можете создать более специализированную абстракцию для этого конкретного случая).

Если хотите, можете поиграть с примером из статьи на CodeSandbox.

Удачи и спасибо за внимание!

ПС. Если вам понравилась эта статья, вам возможно понравится это выступление: youtube Kent C Dodds — Simply React

Источник

Оцените статью