Итак, почему была написана эта статья? Все просто - в других источниках все сложно. Ну к примеру отрывок из Википедии об 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)
Офигенно, Макс проводит сейчас тренинги как раз по этой теме! Будет интересно увидеть твоё виденье!!!
ОтветитьУдалитьПо запросу "что такое dependency injection" твой сайт на первой странице результатов гугла!
ОтветитьУдалитьНу это скорее всего потому, что я в теме и тексте написал "Что такое Dependency injection?". В принципе можно еще захватить вопрос "как пользоваться Dependency injection" или "Dependency injection это просто", то велика вероятность что по словосочетанию Dependency injection я еще немного поднимусь выше :) Но главное не переусердствовать, т.к. гугл не дура и может понять, что я занимаюсь т.н. серой оптимизацией и просто забанит. Если уже не заметил...
ОтветитьУдалитьА вообще возникновение этого поста, скорее следствие вашего тренинга нежели случайное совпадение. Так что спасибо Максу!
хорошая статься ... только обрывается на самом интересном месте :(
ОтветитьУдалитьТак всегда... Ничего, придет время и допишем.
ОтветитьУдалитьЕсли Вас интересует инъекция с помощью reflection, тогда вот пригодится на практике http://apofig.blogspot.com/2010/10/spring-ioc.html
Если же интересует что-то другое, пишите что именно - меня более мотивирует описать то, что уже после отправления поста принесет пользу.
Эх... На самом интересном месте остановился...
ОтветитьУдалитьЕсли есть у тебя в блоге продолженние, прикрепи сюда ссылочку на него :)
Спс
А вот и продолжение. http://apofig.blogspot.com/2011/07/dependency-injection-inversion-of.html
ОтветитьУдалитьПишу его только тогда, когда появляется комментарий и тогда я знаю, что это кому-то нужно.
блин ... все же просто как две копейки...
ОтветитьУдалитьжаль что так поздно наткнулся на статью...
Могу посоветовать книгу http://www.ozon.ru/context/detail/id/6108824/ Вам очень понравится. :)
ОтветитьУдалитьYes, книжка суперская ) Авторский стиль отличается от обычного сухого изложения паттернов
ОтветитьУдалитьКроме того, 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
Где-то встречал еще штуки три...
круто! огромное спасибо!
ОтветитьУдалитьу Вас очень хорошая статья!
ОтветитьУдалитьОказывается, подобное уже применял, но не знал, что это оно и есть!
ОтветитьУдалитьГоворят - "у дураков мысли сходятся". Оказывается для такого подхода и термин придуман :)
ОтветитьУдалить2020 все еще читаем эту статью для обучения. 10 лет....
ОтветитьУдалитьКруто жеж )
Удалить