Говнокодер
0,0
рейтинг
26 марта 2011 в 12:54

Разработка → Инверсия управления/Inversion of Control перевод

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

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

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


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

Однако если бы я использовал оконную систему для чего-то похожего, я написал бы что-то, что работает с окном:
  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). Далее она решает, когда вызвать мои методы, основываясь на связях, которые я настроил при создании формы. Управление инвертировано — управляют мной, а не я управляю фреймворком. Это явление и называется инверсией управления (также известно как Принцип Голливуда — «Не звони нам, мы сами позвоним тебе» — Hollywood Principle — «Don't call us, we'll call you»).

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

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


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

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

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

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

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

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

Примечание перевода, пример:
public class EjbExample implements SessionBean {
	public void ejbActivate() throws EJBException, RemoteException {
		// do some processing on session object became active
	}

	public void ejbPassivate() throws EJBException, RemoteException {
		// do some processing on session object became passive
	}

	public void ejbRemove() throws EJBException, RemoteException {
		// do some processing before the end of life of session object
	}

	public void setSessionContext(SessionContext sessionContext) throws EJBException, RemoteException {
		// interface dependency injection
	}
}

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

Примечание перевода, пример:
public class SomeTest extends TestCase {
	protected void setUp() throws Exception {
		super.setUp();
		// make some preparation for the test, e.g. setup database connection
	}

	public void testSomeLogic() {
		// test some functionality
	}

	protected void tearDown() throws Exception {
		// cleanup after test has finished
		super.tearDown();
	}
}

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

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

UPD: Добавлены примеры.
Перевод: Мартин Фаулер
Иван Холопик @sody
карма
50,0
рейтинг 0,0
Говнокодер
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (25)

  • +1
    Хороший перевод, большое спасибо автору.
  • +3
    Познавательно… вот только в чем мораль? Я не понял)
  • +1
    Слишком много воды в конце. В таких делах нельзя без примеров, иначе все в кашу в голове превращается.
    • +1
      Вроде примеров как раз достаточно.
      Это перевод статьи Фаулера, который, как считается, широко формализовал такие понятия как IoC и DI.
      • +2
        Фаулера, который, как считается, широко формализовал такие понятия как IoC

        А ещё Фаулер задавался вопросом
        The question, is what aspect of control are they inverting?/blockquote>

        У меня, если честно, тоже возникает такой вопрос когда я слышу про IoC. Тот же пример про шаблонный ментод. Экземпляр класса всё так же вызывает метод экземпляра класса-участника иерархии с шаблонными методами. В чём инверсия? Где поменялось направление?
        • 0
          Имхо, изменение нужно искать не на уровне выполнения кода, а на уровне дизайна. В случае с шаблонным методом, когда сам шаблонный метод уже зафиксирован, мы больше явно не можем задать какие функции нужно позвать в каком порядке, т.е. в некотором смысле теряем контроль. Стоит ли называть это инверсией или каким-нибудь другим словом — уже другой вопрос :)
          • 0
            Серёьзно) я не вижу инверсии управления.

            Вася говорит Пете что делать
            Петя говорит Васе что делать

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

            В случае с шаблонным методом — я его действительно не вижу класс-клиент как вызвал методы исполнителя так и вызывает.
            • +1
              Я не вижу «инверсии», но вижу разницу.
              Ещё раз, изменения на уровне выполнения нету. Конечно, заменили один метод другим, он как вызывался так и вызывается.
              Отличие видно, когда мы пишем код, а не выполняем его. Когда я просто пишу последовательность команд, я всё контролирую (когда и какие функции позвать). Когда я использую написанный ранее шаблонный метод, мой контроль над вызовом функций ограничен => потеря контроля.
            • 0
              Добавил примеры. В случае шаблонного метода Фаулер рассматривает JUnit. Мы реализуем методы setUp, tearDown, добавляем тесты (методы testBlaBlaBla), затем для запуска тестов выполняем:
              TestRunner.run(SomeTest.class);
              

              В итоге мы вызываем JUnit, но не вызываем наши методы, JUnit вызывает их, следовательно происходит явление инверсии управления.
      • –1
        Примеры только в начале. Дальше идет рассказ про .net (если бы я не знал, как они там устроены через делегаты, то ничего бы не понял), после чего идет про непонятно что плюс это еще и перевод, так что возможно не все передано точно.

        Как бы хорошо не было формализованно, без примеров понять сложно, особенно когда все в новинку, в пример можно привести учебники математики, где идут подряд одни теоремы на формальном математическом языке. Понять вроде можно, но дается это все с трудом.
        • –1
          Про .net один параграф же вроде. А что, C# делегаты чем-то принципиально отличаются от других механизмов обратного вызова?
          • –1
            Дело в том, что я хорошо знаком с дотнетом, поэтому не понаслышке знаю, как там все это работает, а как в других — я попросту не знаю, поэтому я и говорю, что не хватает примеров.
  • –6
    По-русски IoC традиционно называется «обращение контроля».
    • +6
      Первый раз слышу такое «традиционное» определение
    • –3
      А еще более традиционно IoC называется Ioc
      • +2
        Я все-таки поясню свою позицию. Я против перевода подобных терминов, потому что они порождают перлы вроде ППП (Повсюду Протянутая Паутина). Из-за таких вот переводов технические книги на русском читать невозможно. По-моему как-то на Хабре писали, что в Южной Корее термины не переводятся и к тому же произносятся правильно по-английски (а не как у нас — на российский лад) — вот это классная тема.
    • +1
      «Инверсия управления» точнее отражает суть явления
  • +1
    Воообще-то, это банальные callback. Но «инверсия управления» звучит круче, если хочешь продать свой фреймворк (:
    • +1
      Callback — это лишь одна из разновидностей IoC
      • +4
        Старый и бессмысленный холивар про паттерны и ООП: классы — это замыкания для бедных, или замыкания — это классы для бедных? IoC — это многословное описание костылей, которые приходится применять из-за отсутствия в языках нормальной поддержки функциональщины, или базовый принцип, а функциональный подход обеспечивает одну из возможных реализаций?

        Чтоб в нем участвовать осмысленно, лучше попробовать пописать и так, и так, истина imho где-то посередине все равно.

        С «принципом Голливуда» проблем так-то тоже ведь много, и если можно, то его лучше избегать, а применять только осознанно и при необходимости (это, впрочем, для многих других паттернов справедливо): в данном случае, если не ты звонишь, а тебе звонят, то из контекста понять, что делает конкретный кусок кода, становится сложно, нужно держать всю систему в голове.
        • 0
          Возможно, просто тема не стоит отдельного топика. callback'и как частный случай IoC или наоборот — не суть важно. Эти принципы используются настолько часто, что о них не знает только ленивый. Даже в голову не пришло бы придумывать на этот счёт какую-то особую философию со своим названием. В большинстве мануалов и книг я вижу термин callback, и именно о таком применении идёт речь в статье.
        • 0
          Вот уж правда бессмысленный холивар. Это же семантическая относительность, прям как в физике: смотря, что берем за точку отсчета.

          А про коллбэк я хотел сказать, что IoC не ограничивается коллбэками. Если, конечно, не называть коллбэками в том числе и шаблонные методы.
  • –1
    Как по мне, статья для теоретиков. В плане практического применения гораздо полезнее почитать его же Dependency Injection.
  • 0
    Перевел статью, так как очень мне понравилась. Многие (я тоже до некоторого времени) путают такие принципы как dependency injection и inversion of control. Фаулер в этой статье четко определяет последнее, показавая что dependency injection это всего лишь частный случай реализации inversion of control. Также начал было уже переводить его статью Inversion of Control Containers and the Dependency Injection pattern, но нашел уже готовый перевод в сети
  • 0
    После прочтения в мозгу что-то щелкнуло и все встало на свои места.
    Спасибо.

    P.S. Ну вот почему нельзя было таким простым и понятным языком написать в Википедии?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.