Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


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

пятница, 6 августа 2010 г.

Java for fun: Что такое Dependency injection, Inversion of Control и почему это возникло. Часть #1

Сегодня я общался с чудесным человеком, и имел честь ему рассказывать, как я понимаю эту всю хрень, чаще именуемую как dependency injection. Что такое Dependency injection? Как реализуется Dependency injection в Java? Что такое IoC контейнер (из Spring Framework)? Что такое Java Reflection? Почему шаблон проектирования Простая фабрика называется именно так, а не иначе? И много другого вы узнаете из этого поста. Пишу его, пока свежа память. Читаем дальше...

Итак, почему была написана эта статья? Все просто - в других источниках все сложно. Ну к примеру отрывок из Википедии об Inversion of Control:
Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Класс X зависит от класса Y, если выполняется одно из следующих условий:
* X has-a Y и вызывает его
* X is-a Y
* X зависит от некоторого класса Z, который зависит от Y (принцип транзитивности)

X зависит от Y не значит, что Y зависит от X. Если же существуют обе зависимости, то это называется циклической зависимостью: X не может быть использован без Y, и наоборот. Существование большого числа циклических зависимостей в объектно-ориентированной программе может быть показателем не оптимального программного построения.

Если объект x (класса X) вызывает методы объекта y (класса Y), то X зависит от Y. Зависимость может быть обращена введением третьего класса, а именно интерфейсного класса I, который должен содержать все методы, которые x может вызвать у y. Кроме того, Y должен реализовать интерфейс I. X и Y сейчас оба зависят от I, и класс X более не зависит от класса Y; предполагается, что X не реализует Y.

Это исключение зависимости класса X от Y введением интерфейса I называется Inversion of Control (или Dependency Injection).

Следует отметить, что Y может зависеть от других классов. До внесения изменений X зависел от Y, таким образом X косвенно зависел от всех классов, от которых зависит Y. Применением Inversion of Сontrol все эти косвенные зависимости также были разорваны — не только зависимость X от Y. Новый интерфейс I ни от чего не зависит.
Дело ясное, что дело темное. Мне лично понятно, что тут написано, но все же приходится напрячься. А каково было бы моей Бабушке въезжать во все это?

Я всегда считал (и считаю), что стоит начинать с истории возникновения чего либо. Только пройдя весь путь эволюции, можно ясно понять, почему был придуман именно этот инструмент и именно так, а не иначе. Когда ты повторно изобретаешь колесо, ты лучше понимаешь почему оно крутится.

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

Чуть позже придумали объектно ориентированный язык программирования вместе с ее инкапсуляцией, чем здоровско повлияли на эту самую "сильную связанность" (в сторону ее ослабления, естественно). Теперь словами процедурщика, код объединялся в некоторые хранилища кода (далее классы), совместно объединенных по одному принципу - "вместе используем данные - вместе живем (в классе)", и, лишь небольшая часть этих процедур (а правильнее методов, ибо методы обработки данных) была видна внешнему миру - их стали называть публичные. А все остальные методы, которые используются внутри класса, но не видны извне - приватные. Теперь программист сам решал, какой код из класса можно повторно использовать другим классам (публичные методы), а какой нет (приватные методы).
Стоит ввести новое понятие интерфейсной части - как набор публичных методов одного класса, и, как следствие повторного использования кода через интерфейсную часть класса - зависимость классов. Теперь связан код сильно или нет, стали определять в основном по степени зависимости классов.
Ладно, когда связь односторонняя, но бывают случаи когда связь двухсторонняя. Яблоко знает про червяка и червяк знает про яблоко, а вместе все они знают про меня, собравшегося надкусить это яблоко. Сложновато. Первым делом избавляются от двухсторонней зависимости в сторону односторонней. Так, чтобы яблоко кушалось червяком не осознавая того, а я кушал их обоих не спрашивая у них разрешения.


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

Тут стоит ввести понятие интерфейса и ее реализации. Батарейка - интерфейс. А реализация ее может быть разная: алкалайн, 777, NiMH, Li-ion, U235 или что-то еще... Те параметры, на основе которых ты утверждаешь, что этот объект - "батарейка", являются методами этого объекта: высота, цилиндрическая форма, напряжение эллектричесого тока на полюсах. наличия двух полюсов, на одном из которых пупырышка... Словосочетание "это батарейка" (или правильнее этот объект ведет себя как батарейка),в ООП стоит понимать как объект реализует интерфейс "батарейка".


 Итак программист вдруг захотел, чтобы взамен батарейки 777 его система начала принимать NiMH и любые другие элементы питания. И тут же столкнулся с проблемой - они вроде как и схожи между собой, но все же имеют разную форму, вольтаж, ампераж... Программисту пришлось выделить интерфейс как независимую единицу и сказать: "отныне все батарейки должны соответствовать этому ГОСТУ" (ну или они не смогут работать с моей системой). Он опубликовал интерфейс и освободил себя от головной боли с вопросом "как вот эту дрыну засунуть в мой фотик?"
Так пропала зависимость между конкретной реализацией и клиентским кодом, который ее использует. Часто на диаграммах это рисуют так:
Было:
Стало:
А с несколькими разными реализациями как-то так
Теперь все зависит от интерфейса. Такой подход изменения зависимостей с введением интерфейса называется Inversion of Control. В этом подходе интерфейс стоит понимать как некий договор с описанием того, как должен вести себя один объект если он хочет использоваться другим.

Но тут не все так просто. Хорошо, если мы сможем полностью заменить в классе-клиенте все места, в которых упоминается, класса-реализация на его интерфейс. Но может остаться как минимум одно место, в котором все же будет упоминаться класс-реализация. Это место, где этот класс-реализация создается. Выглядеть она будет приблизительно так:

Батарейка батарейка = new АлкалайноваяБатарейка();

Зависимость теперь определяется одной этой строчкой и мы можем вынести ее в отдельный метод (простая фабрика)

public final class ФабрикаБатареек {
     public static Батарейка получитьБатарейку(ТипБатарейки тип) {
        if (тип.совпадает(ТипБатарейки.АЛКАЛЙНОВАЯ)) {
            return new АлкалайноваяБатарейка();
        } else {
        ....
    }

В таком случае картинка зависимостей немного поменяется:
Это уже лучше. Клиент ничего не знает ни про одну реализацию, зато хорошенько знаком с фабрикой батареек. Cо временем и эту зависимость наш программист захотел убить. Ну как вы себе представляете, что фотоаппарат знает про фабрику? Фотоаппарат может содержать батарейку, а может и не содержать, но знать про фабрику фотоаппарату нежелательно. И тут программисту приходит идея, а что если будет некий НИКТО, кто эту батарейку незаметно вставит в фотоаппарат, а потом мы скажем, что так всегда и было. Вероятно, задача НИКТО заключалась бы в инъекции пары батареек в фотоаппарат в момент получения нового экземпляра фотоаппарата незаметно для всех (в том числе и самого фотоаппарата). И что делать? Пользоваться приемами dependency injection!

А как пользоваться, читайте в следующем выпуске... (а пока выпуск готовится, может заинтересует конкретный пример инъекции объекта private в поле другого объекта с помощью reflection)

15 комментариев:

  1. Офигенно, Макс проводит сейчас тренинги как раз по этой теме! Будет интересно увидеть твоё виденье!!!

    ОтветитьУдалить
  2. По запросу "что такое dependency injection" твой сайт на первой странице результатов гугла!

    ОтветитьУдалить
  3. Ну это скорее всего потому, что я в теме и тексте написал "Что такое Dependency injection?". В принципе можно еще захватить вопрос "как пользоваться Dependency injection" или "Dependency injection это просто", то велика вероятность что по словосочетанию Dependency injection я еще немного поднимусь выше :) Но главное не переусердствовать, т.к. гугл не дура и может понять, что я занимаюсь т.н. серой оптимизацией и просто забанит. Если уже не заметил...

    А вообще возникновение этого поста, скорее следствие вашего тренинга нежели случайное совпадение. Так что спасибо Максу!

    ОтветитьУдалить
  4. хорошая статься ... только обрывается на самом интересном месте :(

    ОтветитьУдалить
  5. Так всегда... Ничего, придет время и допишем.

    Если Вас интересует инъекция с помощью reflection, тогда вот пригодится на практике http://apofig.blogspot.com/2010/10/spring-ioc.html

    Если же интересует что-то другое, пишите что именно - меня более мотивирует описать то, что уже после отправления поста принесет пользу.

    ОтветитьУдалить
  6. Эх... На самом интересном месте остановился...
    Если есть у тебя в блоге продолженние, прикрепи сюда ссылочку на него :)
    Спс

    ОтветитьУдалить
  7. А вот и продолжение. http://apofig.blogspot.com/2011/07/dependency-injection-inversion-of.html

    Пишу его только тогда, когда появляется комментарий и тогда я знаю, что это кому-то нужно.

    ОтветитьУдалить
  8. Анонимный21 июля 2011 г., 20:22

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

    ОтветитьУдалить
  9. Могу посоветовать книгу http://www.ozon.ru/context/detail/id/6108824/ Вам очень понравится. :)

    ОтветитьУдалить
  10. Yes, книжка суперская ) Авторский стиль отличается от обычного сухого изложения паттернов

    ОтветитьУдалить
  11. Кроме того, Head first - это серия книг издательства O'Reilly

    http://oreilly.com/store/series/headfirst.csp?

    В этом исполнении есть уже пару десятков книг.

    В русиш переводе от Питера есть пару

    http://www.piter.com/search/index.php?q=head+first&s.x=0&s.y=0&s=%CF%EE%E8%F1%EA&ext=&inSaleOnly=0&searchAs=0&orderBy=r&order=ASC&itemsPerPage=10

    Есть PHP

    http://notabenoid.com/book/16203

    Где-то встречал еще штуки три...

    ОтветитьУдалить
  12. у Вас очень хорошая статья!

    ОтветитьУдалить
  13. Оказывается, подобное уже применял, но не знал, что это оно и есть!

    ОтветитьУдалить
  14. Говорят - "у дураков мысли сходятся". Оказывается для такого подхода и термин придуман :)

    ОтветитьУдалить