View on GitHub

wiki

Technical Excellence Wiki

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

Перевод статьи на русский язык Мартина Фаулера InversionOfControl.

Инверсия управление (англ. Inversion of Control, IoC) - распространенное явление, с которым вы сталкиваетесь при расширении фреймворков. На самом деле оно часто рассматривается как определяющая характеристика фреймворка.

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

puts 'What is your name?'
name = gets
process_name(name)
puts 'What is your quest?'
quest = gets
process_quest(quest)

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

Однако если бы я использовал оконный графический интерфейс, чтобы сделать что-то подобное, я бы начал с конфигурации объекта Window.

require 'tk'
root = TkRoot.new()
name_label = TkLabel.new() {text "What is Your Name?"}
name_label.pack
name = TkEntry.new(root).pack
name.bind("FocusOut") {process_name(name)}
quest_label = TkLabel.new() {text "What is Your Quest?"}
quest_label.pack
quest = TkEntry.new(root).pack
quest.bind("FocusOut") {process_quest(quest)}
Tk.mainloop()

Эти примеры значительно отличаются потоками управления между - в частности, когда process_name и process_quest методы будут вызваны. В первой программе я решаю, когда эти методы будут вызваны, но во втором случае я этого не делаю. Вместо этого я управляю оконной подсистемой (с помощью команды Tk.mainloop). А уже потом она решает, когда вызвать мои методы, основываясь на привязках, которые я сделал при создании формы. Управление инвертировано — фреймворк вызывает меня, а не я его. Это явление называется Инверсией управление (также известное, как Голливудский принцип - “Не звоните нам, мы сами вам позвоним”).

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

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

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

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

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

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

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

Это сложные случаи инверсии управления, но вы можете столкнуться с этим эффектом в гораздо более простых ситуациях. Хороший пример — шаблонный метод: суперкласс определяет поток управления, подклассы расширяют эти методы переопределения или реализуют абстрактные методы при расширении. Так, в JUnit2 код фреймворка вызывает методы setUp и tearDown, чтобы вы могли создать и очистить фикстуру без лишнего кода. Он выполняет вызов, ваш код реагирует — поэтому снова управление инвертировано.

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

Этимология: Насколько я могу судить, термин “инверсия управления” впервые появился в статье Ральфа Джонсона и Брайана Фута “Designing Reusable Classes”, опубликованной в “Journal of Object-Oriented Programming” в 1988 году. Эта статья относится к числу хорошо выдержанных — она его стоит прочитать сейчас, спустя несколько десятков лет. Авторы считают, что позаимствовали этот термин откуда-то ещё, но не могут вспомнить, откуда именно. Затем этот термин проник в объектно-ориентированное сообщество и появился в книге “Банды четырех”. Более красочный синоним “Голливудского принципа”, кажется, берёт свое начало в статье Ричарда Свита в MESA3 в 1983 году. Описывая цели проектирования он пишет:

Не звоните нам, мы сами вам позвоним (Голливудский принцип): инструмент должен организовать, чтобы Tajo уведомлял его, когда пользователь желает сообщить инструменту о каком-либо событии, вместо того, чтобы использовать модель “запросить у пользователя команду и выполнить ее”.

Джон Влиссидес написал колонку для отчета4 о C++, которая даёт хорошее объяснение концепции под названием “Голливудский принцип” (Спасибо Брайану Футу и Ральфу Джонсону за помощь мне с этой информацией).

Заметки

  1. EJB (Enterprise Java Beans) - часть платформы Java EE. В года публикации статьи (2005) была прорывной технологией. Со временем уступила место более гибким и легковесным подходам, например Spring Framework и Google Guice.

  2. Пример приведён для JUnit версии 3 и ниже. В более поздних версиях появилась возможность вместо расширения класса TestCase использовать аннотация для методов. Что не меняет суть концепции.

  3. MESA International (англ. Manufacturing Enterprise Solutions Association) — всемирная некоммерческая ассоциация разработчиков, системных интеграторов, экспертов и пользователей решений для промышленных предприятий (решений MES).

  4. Оригинальная статья была размещена по адресу “http://www.research.ibm.com/designpatterns/pubs/ph-feb96.txt”. Но на момент перевода данная ссылка не вела к ней.

Перевёл: Кротов Артём.

Остались вопросы? Задавай в нашем чате.