Инверсия контроля (Inversion of control, IoC)

В программной инженерии инверсия контроля (Inversion of control, IoC) является принципом программирования. IoC инвертирует поток управления по сравнению с традиционным потоком управления. В IoC пользовательские части компьютерной программы получают поток управления из общей структуры. Архитектура программного обеспечения с таким дизайном инвертирует управление по сравнению с традиционным процедурным программированием: в традиционном программировании пользовательский код, выражающий цель программы, обращается к библиотекам многократного использования для решения общих задач, но с инверсией контроля фреймворк вызывает пользовательский или специфический для задачи код.

Инверсия контроля используется для повышения модульности программы и ее расширения и применяется в объектно-ориентированном программировании и других парадигмах программирования. Этот термин был использован Майклом Мэттссоном в диссертации, взятой оттуда Стефано Маццокки и популяризированной им в 1999 году в уже несуществующем проекте Apache Software Foundation, Avalon, который впоследствии был популяризирован в 2004 году Робертом К. Мартином и Мартином Фаулером.

Этот термин связан, но отличается от принципа инверсии зависимостей (dependency inversion principle), который связан с разделением зависимостей между слоями высокого и низкого уровня с помощью общих абстракций. Общая концепция также связана с программированием, управляемым событиями (event-driven programming), в том смысле, что оно часто реализуется с использованием IoC, так что пользовательский код обычно касается только обработки событий, тогда как цикл обработки событий и отправка событий/сообщений обрабатываются фреймворком или средой выполнения.

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

С другой стороны, при инверсии контроля программа будет написана с использованием программного фреймворка, который знает общие поведенческие и графические элементы, такие как системы управления окнами, меню, управление мышью и т. д. Пользовательский код "заполняет пробелы" для фреймворка, например, предоставляет таблицу пунктов меню и регистрирует подпрограмму кода для каждого элемента, но это фреймворк отслеживает действия пользователя и вызывает подпрограмму при выборе элемента меню. В примере с почтовым клиентом фреймворк может следовать за вводами с клавиатуры и мыши и вызывать команду, вызванную пользователем любым из способов, и в то же время контролировать сетевой интерфейс, чтобы узнать, поступают ли новые сообщения, и обновлять экран, когда некоторые сетевая активность обнаружена. Тот же самый фреймворк может использоваться в качестве каркаса для программы работы с электронными таблицами или текстового редактора. И наоборот, фреймворк ничего не знает о веб-браузерах, электронных таблицах или текстовых редакторах; реализация их функциональности требует специального кода.

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

Инверсия контроля служит следующим целям проектирования:

  • Отделить выполнение задачи от реализации.
  • Сфокусировать модуль на задаче, для которой он предназначен.
  • Освободить модули от предположений о том, как другие системы делают то, что они делают, и вместо этого полагаться на контракты.
  • Предотвратить побочные эффекты при замене модуля.

Инверсию контроля иногда шутливо называют "принципом Голливуда: не звоните нам, мы вам позвоним".

Бэкграунд

Инверсия контроля не является новым термином в информатике. Мартин Фаулер прослеживает этимологию этой фразы еще в 1988 году, но она тесно связана с концепцией инверсии программы, описанной Майклом Джексоном в его методологии структурированного программирования Джексона в 1970-х годах. Восходящий синтаксический анализатор можно рассматривать как инверсию нисходящего синтаксического анализатора: в одном случае управление принадлежит парсеру, а в другом случае - приложению-получателю.

Инъекция зависимости является специфическим типом IoC. Сервисный локатор, такой как Java Naming and Directory Interface (JNDI), аналогичен. В статье Лука Бергмана он представлен как архитектурный принцип.

В статье Роберта Мартина принцип инверсии зависимостей и абстрагирование по слоям объединяются. Его причина использовать термин "инверсия" - для сравнения с традиционными методами разработки программного обеспечения. Он описывает разобщение услуг путем абстракции уровней, когда говорит об инверсии зависимостей. Этот принцип используется для определения границ системы при проектировании уровней абстракции.

Описание

В традиционном программировании поток бизнес-логики определяется объектами, которые статически связаны друг с другом. При инверсии контроля поток зависит от графа объектов, который строится во время выполнения программы. Такой динамический поток становится возможным благодаря объектным взаимодействиям, которые определяются посредством абстракций. Эта привязка во время выполнения достигается такими механизмами, как внедрение зависимостей или поиск сервиса. В IoC код также может быть статически связан во время компиляции, но для нахождения кода, который нужно выполнить, считывая его описание из внешней конфигурации, а не с помощью прямой ссылки в самом коде.

При внедрении зависимости зависимый объект или модуль связывается с нужным ему объектом во время выполнения. Какой конкретный объект будет удовлетворять зависимости во время выполнения программы, обычно неизвестно во время компиляции с использованием статического анализа. Хотя этот принцип описан в терминах взаимодействия объектов, этот принцип может применяться к другим методологиям программирования, помимо объектно-ориентированного программирования.

Для того чтобы запущенная программа связывала объекты друг с другом, эти объекты должны иметь совместимые интерфейсы. Например, класс A может делегировать поведение интерфейсу I, который реализуется классом B; программа создает экземпляры A и B, а затем вводит B в A.

Методы реализации

В объектно-ориентированном программировании есть несколько основных методов для реализации инверсии контроля:

  • Использование паттерна поиска сервисов (service locator pattern)
  • Используя внедрение зависимости, например:
    • Внедрение в контрукторе
    • Внедрение параметром
    • Внедрение через сеттер
    • Внедрение через интерфейс
  • Использование контекстного поиска
  • Использование паттерна шаблонного метода
  • Использование паттерна стратегии

В оригинальной статье Мартина Фаулера обсуждаются первые три различных метода. Часто контекстуальный поиск будет выполняться с использованием сервисного локатора.

Примеры

Большинство сред, таких как .NET или Enterprise Java, отображают этот паттерн:

public class ServerFacade {
    public  V respondToRequest(K request) {
        if (businessLayer.validateRequest(request)) {
            Data data = DAO.getData(request);
            return Aspect.convertData(data);
        }
        return null;
    }
}

Эта базовая схема в Java дает пример кода, следующего методологии IoC. Однако важно, чтобы в ServerFacade было сделано много предположений относительно данных, возвращаемых объектом доступа к данным (DAO).

Хотя все эти предположения могут быть действительными в какое-то время, они связывают реализацию ServerFacade с реализацией DAO. Разработка приложения в форме инверсии контроля полностью передаст управление объекту DAO. Код станет таким:

public class ServerFacade {
    public  V respondToRequest(K request, DAO dao) {
        return dao.getData(request);
    }
}

В примере показано, что способ, которым создается метод respondToRequest, определяет, используется ли IoC. Это способ, которым используются параметры, которые определяют IoC. Это напоминает стиль передачи сообщений, который используют некоторые объектно-ориентированные языки программирования.


Читайте также:

Комментарии

Популярные сообщения из этого блога

Язык поисковых запросов в Graylog

Хэш-таблица: разрешение коллизий

Нормальные формы, пример нормализации в базе данных