Перевод статьи на русский язык Мартина Фаулера 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++, которая даёт хорошее объяснение концепции под названием "Голливудский принцип" (Спасибо Брайану Футу и Ральфу Джонсону за помощь мне с этой информацией).
-
EJB (Enterprise Java Beans) - часть платформы Java EE. В года публикации статьи (2005) была прорывной технологией. Со временем уступила место более гибким и легковесным подходам, например Spring Framework и Google Guice.
-
Пример приведён для JUnit версии 3 и ниже. В более поздних версиях появилась возможность вместо расширения класса
TestCase
использовать аннотация для методов. Что не меняет суть концепции. -
MESA International (англ. Manufacturing Enterprise Solutions Association) — всемирная некоммерческая ассоциация разработчиков, системных интеграторов, экспертов и пользователей решений для промышленных предприятий (решений MES).
-
Оригинальная статья была размещена по адресу "http://www.research.ibm.com/designpatterns/pubs/ph-feb96.txt". Но на момент перевода данная ссылка не вела к ней.
Перевёл: Кротов Артём.
Остались вопросы? Задавай в нашем чате.