Инверсии зависимостей управления впрыском

    image

    Вступление


    Наверняка первый вопрос, который возник у вас при взгляде на заголовок, был "Шта?". На самом деле я просто перевел фразу "Инверсия управления, внедрение зависимости" в Google Translate на китайский, а затем обратно. Зачем? Затем, что на мой взгляд, это хорошая иллюстрация того, что происходит на самом деле. Люди вокруг путают, коверкают и извращают эти понятия. По долгу службы я провожу много интервью, и 90% того, что я слышу, когда задаю вопрос про DI — честно говоря, откровенный бред. Я сделал поиск по Хабру и нашел несколько статей, которые пытаются раскрыть эту тему, но не могу сказать, что они мне сильно понравились (ладно, ладно, я проглядел только три первых страницы, каюсь). Здесь же на Хабре я встречал в комментариях такую расшифровку IoC, как Injection of Container. Кто-то всерьез предполагает, что есть некий механизм инъекции контейнеров, который сосуществует где-то рядом с DI, и, видимо, даже делает нечто похожее. Только с контейнерами. Мда. На самом деле понять внедрение зависимости очень просто, надо всего лишь…

    Удивительно, но факт — эта штука со «всего лишь...» действительно работает! Иначе вы бы здесь не оказались, не так ли?

    Ричард Фейнман был удивительным рассказчиком, умевшим ясно и доступно объяснять весьма сложные вещи (посмотрите, хотя бы, это видео). Джоэл Спольски считает, что по-настоящему умный программист обязательно должен уметь изъясняться на человеческом языке (а не только на Си). Ну и, наверное, практически каждому известен афоризм Альберта Эйнштейна: "Если вы что-то не можете объяснить шестилетнему ребёнку, вы сами этого не понимаете". Конечно же, я не собираюсь сравнивать вас с шестилетними детьми, но тем не менее постараюсь рассказать про DI, IoC и еще один DI максимально просто и понятно.

    NB. А вы знали, что помимо внедрения зависимости через конструктор и сеттер, существует еще и третий способ — внедрение посредством интерфейса? Хоть это и выходит за рамки данной статьи, но, держу пари, что либо вы сейчас открыли для себя кое-что новенькое, либо по крайней мере опустили уже заготовленный тухлый помидор.

    Инверсия управления (Inversion of Control)


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

    Однако, к сожалению, выходные заканчиваются, наступает понедельник, и приходится идти на работу (если, конечно, она у вас есть). По условиям трудового договора вы должны быть на месте в 8 утра. Вы работаете до полудня. Потом у вас перерыв на обед, а затем еще четыре часа кипучей деятельности. Наконец, в 17:00 вы выбираетесь из офиса и отправляетесь домой, где снова можете расслабиться и вмонтировать пивандрия. Чувствуете разницу? Вы больше не управляете своим дневным расписанием, это делает кое-кто другой — ваш работодатель.

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

    Но в один прекрасный день в кабинет входит босс и сообщает пренеприятнейшее известие — консоль больше не в моде, миром правят графические интерфейсы, а значит все надо переделать. Будучи современным и гибким (речь не только о ваших занятиях йогой) программистом, вы сразу принимаетесь за внесение изменений. Для этого вы подключаете GUI-фреймворк и пишете код обработки событий. Если нажата вот эта кнопка, то надо сделать то-то и то-то. А если пользователь изменил свой выбор в выпадающем списке, то не обойтись без вот этого и этого. Все идет хорошо, но тут вы понимаете, что раньше было как-то по-другому. А кто, собственно, вызывает эти обработчики событий, которые вы так усердно программируете? Кто вообще определяет, куда и когда нажал пользователь? Что вообще происходит? Где мои носки? GUI-фреймворк оказался явно хитрее, чем вы думали, и перехватил у вас управление потоком выполнения приложения.

    Это и есть Inversion of Control — очень абстрактный принцип, постулирующий факт задания потока выполнения некой внешней по отношению к вам сущностью.

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

    В качестве домашнего задания поразмышляйте, почему Джефф Сазерленд настаивает на том, что SCRUM — это именно фреймворк, а не методология.

    Инверсия зависимости (Dependency Inversion)


    Это та самая буква D в аббревиатуре SOLID — принцип, говорящий о том, что:

    • модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций;
    • абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

    Немного путаная формулировка, поэтому рассмотрим следующий пример (для примеров я буду использовать C#).

    public class Foo {
      private Bar itsBar;
    
      public Foo() {
         itsBar = new Bar();
      }
    }

    Проблема здесь заключается в том, что класс Foo зависит от конкретного класса Bar. По той или иной причине — в целях расширяемости, переиспользуемости или тестируемости ради — может встать задача их разделить. Согласно принципу инверсии зависимости для этого следует ввести между ними промежуточную абстракцию.

    public class Foo {
      private IBar itsBar;
    
      public Foo() {
         itsBar = new Bar();
      }
    }

    Диаграмма UML наглядно демонстрирует оба варианта.

    image

    Сложности начинаются, когда спрашиваешь, а где же здесь, собственно, инверсия? Основополагающая идея, без понимания которой невозможно ответить на этот вопрос, заключается в том, что интерфейсы принадлежат не своим реализациям, а использующим их клиентам. Имя интерфейса IBar вводит в заблуждение и заставляет рассматривать связку IBar + Bar как единое целое. В то же время истинным владельцем IBar является класс Foo, и если принять это во внимание, то направление связи между Foo и Bar действительно обратится вспять.

    image

    Внедрение зависимости (Dependency Injection)


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

    public class Foo {
      private IServer itsServer;
    
      public Foo() {
         itsServer = new Bar();
      }
    }

    Чтобы избавить класс Foo от такой неприятной обязанности, хорошо было бы вынести код инстантинации куда-то в другое место и инкапсулировать его там (поскольку все мы чрезвычайно прагматичны и не любим ничего писать дважды). Сделать это можно двумя способами — используя либо Service Locator, либо Dependency Injection.

    Service Locator — это такой реестр соответствия абстракций и их реализаций. Вы скармливаете ему интересующий вас интерфейс, а в ответ получаете готовый экземпляр конкретного класса. Выглядит это примерно так:

    public class Foo {
      private IServer itsServer;
    
      public Foo() {
         itsServer = ServiceLocator.Resolve<IServer>();
      }
    }

    Нюанс заключается в том, что класс Foo теперь совершенно не зависит от класса Bar, но по-прежнему управляет его инстантинацией. Как мы уже знаем, избежать этого можно инвертировав поток управления, т.е. передав оное управление в руки некоего внешнего механизма. Dependency Injection и является таким механизмом, реализуемым в виде фреймворков под названием IoC-контейнеры:

    public class Foo {
      private IServer itsServer;
    
      public Foo(IServer server) {
         itsServer = server;
      }
    }

    Заключение


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

    Подытожим. Мы используем Dependency Inversion, чтобы разделить модули абстракцией, Dependency Injection, чтобы избавиться от инстантинации вручную, а реализуем это посредством фреймворка, построенного по принципу Inversion of Control. И ничто из этого не является синонимами, поэтому IoC-контейнеры — яркий пример того, как можно намертво всех запутать с помощью одного единственного неудачного термина.

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

    Ну. И что?
    Реклама
    Комментарии 51
    • +1
      В то же время истинным владельцем IBar является класс Foo, и если принять это во внимание, то направление связи между Foo и Bar действительно обратится вспять.
      Нелогично. Интерфейс, на то и интерфейс, что им никто не владеет, а он просто описывает абстракцию или какую то общую идею. Почему кто-то кем-то должен владеть то?

      Нюанс заключается в том, что класс Foo теперь совершенно не зависит от класса Bar
      но зависит от ServiceLocator, от которого таким образом начинает зависеть буквально каждый метод, а значит переиспользовать код, не переиспользовав ServiceLocator — невозможно. Раньше зависимость была прямо в классе, теперь — она где то в инициализации приложения видимо. Потому что неизвестно кто где и как заполняет ассоциации ServiceLocator.
      • +2
        Нелогично. Интерфейс, на то и интерфейс, что им никто не владеет, а он просто описывает абстракцию или какую то общую идею. Почему кто-то кем-то должен владеть то?

        Такова была идея автора этого принципа — Роберта Мартина. Если эту идею игнорировать, то вообще непонятно, что за инверсия там имеется в виду. В общем-то это чистая условность.

        но зависит от ServiceLocator, от которого таким образом начинает зависеть буквально каждый метод, а значит переиспользовать код, не переиспользовав ServiceLocator — невозможно

        Совершенно верно, а еще зависимость от IBar/IServer так и остается для клиента класса Foo неявной.
        • 0
          Нелогично. Интерфейс, на то и интерфейс, что им никто не владеет, а он просто описывает абстракцию или какую то общую идею. Почему кто-то кем-то должен владеть то?

          Абстракция или общая идея никому не нужна. :) Но в общем и в целом реализуемые провайдером интерфейсы определяются (в идеальном мире) требованиями клиентов. Если провайдер реализует какой-то интерфейс, который клиент не использует, то интерфейс останется безхозным. :)
          • +1
            Интерфейс может быть требованием клиента, а может — особенностями реализации с сервера.

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

                В рамках темы статьи, да, скорее сервер (ну или более низкий слой) считается поставщиком интерфейса.
          • Что означает «класс является владельцем интерфейса»? С точки зрения с#, я так понимаю, это означает: в какой сборке должен быть описан интерфейс…

            Понятное дело, не в сборке где и его реализация, ибо реализаций интерфейса может быть несколько.
            Но и не у клиента. Несколько классов могут использовать интерфейс. Например, IList или IDisposable.
            Получается логично описывать интерфейс в отдельной сборке и интерфейс никому не принадлежит.

            С другой стороны есть такой сценарий использования: сборка содержит несколько реализаций интерфейса и сам интерфейс, а наружу поставляет объекты некоторой фабрикой. Получается такая самодостаточная библиотечка. Но тогда, чтобы расширить множество реализаций этого интерфейса, то придется тянуть за собой и старые реализации, что не всегда надо. Таким образом опять лучше держать интерфейс в отдельной сборке.

            В общем, интерфейс он сам по себе…
            • +1
              Понятие владения исключительно абстрактное. А про сборки посмотрите того же Мартина, у него есть обширная теоретическая база на эту тему в «Принципах, паттернах и методиках гибкой разработки на языке C#».
              • 0

                Можете вкратце описать его идею?

                • 0
                  Если совсем вкратце, то можно вот тут почитать — http://dimakudr.blogspot.ru/2010/07/blog-post.html (плюс там еще вторая часть есть).
        • НЛО прилетело и опубликовало эту надпись здесь
          • +2
            Каждый получатель, несмотря на то, что получает объект IPlayer, вынужден указывать, объект какого типа (чтиай: какого класса) он хочет получить

            Значит ему не нужен интерфейс, а нужен конкретный класс.

            Суть в том, что получателю все равно какую конкретную реализацию ему дадут, он сможет работать с любой.
            • НЛО прилетело и опубликовало эту надпись здесь
              • +6
                Если данные идеологически разные, то зачем их в один интерфейс пытаться объединить?

                А если данные одинаковы, но просто разные способы получения этих данных, допустим через разные каналы связи, то потребителю как раз не важно, как именно получаются данные, он знает, что предоставленный ему объект имеет интерфейс для получения этих данных.
            • +1
              Я знаю, что я таким ответом себе не заработаю популярности, но советую прочесть Dependency Injection in .NET.
              • НЛО прилетело и опубликовало эту надпись здесь
                • +1
                  О, в случае этой книги это совершенно не принципиально.
              • 0
                В таком случае, как мне кажется, механизм «фоновой» инстантинации, которую предлагает DI, вам просто не подходит. Наличие DI-фреймворка в системе не означает же, что вообще ВСЕ зависимости надо прогонять через него. В вашем инструментарии всегда есть те же фабрики и фабричные методы, если нужно инкапсулировать инстантинацию в одном месте, но при этом клиентский код должен ее контролировать.
                • НЛО прилетело и опубликовало эту надпись здесь
                  • 0
                    Тогда, когда можно, грубо говоря, «один раз сконфигурировать и забыть», избавившись таким образом от написания оператора new или вызовов Service Locator'а каждый раз, когда вам нужен экземпляр класса, на который указывает зависимость. Здесь, скорее, стоит вопрос, когда надо использовать инверсию зависимостей, а когда нет, а это отдельная большая тема для дискуссий.
                    • 0
                      Использование DI предполагает, что вашем клиенту все равно какая реализация IPlayer будет ему предоставлена. Классический пример, это тестирование — например, вам не нужно чтобы тест слал статистику по игрокув Windows Store, и вы поставляете класс реализующий IPlayer, но не взаимодействущий с сервисом Windows Store.
                • 0
                  Представьте себе ваше приложение, разделите его условно на слои. Например слой доступа к данным, бизнес логика, пользовательский интерфейс. Теперь когда вы провели грань, попробуйте их описать так, что бы каждый слой был максимально независим от другого. IoC это собственно и есть способ реализовать ваши слои независимыми друг от друга. Dependency Injection и Service Locator являются способами реализации самого контейнера. При этом Service Locator считается анти-паттерном, так как ведёт к тому что наши слои будут зависимы от конкретного контейнера, что в принципе немного не укладывается в саму суть IoC контейнера. Хотелось бы увидеть побольше статей на эту тему, так как дискутировать можно очень долго
                  • +3
                    IoC это собственно и есть способ реализовать ваши слои независимыми друг от друга

                    Чтобы сделать слои независимыми друг от друга, надо разорвать связь, т.е. убрать ссылку на класс B из класса A вообще :) Но это не в наших интересах, т.к. тогда приложение не сможет выполнять свои функции. В наших интересах эту связь ослабить, а делается это путем введения промежуточной абстракции (интерфейса или абстрактного класса). Понадобиться это может, если мы захотим, например, протестировать класс A изолированно от класса B (тогда мы заменим B фэйковой реализацией (моком) и подсунем его A). Или если мы предполагаем, что в дальнейшем мы можем использовать в системе другую реализацию B, но при этом не хотим, чтобы изменения вообще хоть как-то касались класса А. А вот как мы собираемся инстанцировать класс B — это уже вопрос отдельный, и именно при ответе на него и появляются фабрики, сервис локаторы и депенденси инжекшены.
                    • 0
                      Чтобы сделать слои независимыми друг от друга, надо разорвать связь, т.е. убрать ссылку на класс B из класса A вообще :)

                      Так введение интерфейса IB в слое, где лежит класс A и убирает ссылку на класс B из класса A. Класс А зависит теперь только от абстрактной (в большинстве случаев) сущности (в широком смысле слова) в своём слое. Дело клиентов слоя класса A (верхний слой, приложение в целом, тестовый фреймворк и т. п.) передать классу A нужную им зависимость явно, посредством IoC-контейнера или ещё как. Класс B теперь зависимость другого слоя, того же (или выше) зависимостью которого является класс A, в слое класса A зависимости от вышележащих слоев теперь нет, он самодостаточный (если это самый нижний слой), его можно скомпилировать в отдельную сборку/бандл, к которой не нужно линковать вышележащие слои.
                      • 0
                        С тем маленьким нюансом, что изначально слой с А зависел от слоя с B, а после введения IB уже слой с B стал зависеть от слоя с А. Таким образом мы не разрываем полностью связь между слоями, а лишь ослабляем ее и меняем направление, получая слабое зацепление. В результате получается подобная штука — https://habrahabr.ru/post/269589/
                        • +1
                          Как разработчикам слоя А нам всё равно, что клиенты слоя A зависели только от A, может даже не догадываясь, что есть B, а теперь зависят от A и IB :) Мы-то от B не зависим больше, а они как зависели от нашего слоя, так и зависят.

                          Да, фразу «делать слои независимыми друг от друга» в контексте поста не надо понимать буквально, я думаю, а надо как «делать слои нижнего уровня независимыми от слоев верхнего уровня».
                  • 0
                    public class Foo {
                      private IBar itsBar;
                    
                      public Foo() {
                         itsBar = new Bar();
                      }
                    }
                    

                    Читать как

                    public class Foo {
                      private IBar itsBar;
                    
                      public Foo() {
                         itsBar = new IBar();
                      }
                    }
                    

                    или я чего то не понимаю?
                    • 0
                      Согласен, не понимаю. Теперь понял. Прошу не пинать, сообщение уже не удаляется.
                      • 0
                        Да, использована майкрософтовская конвенция добавлять к именам интерфейсов букву I, раз уж примеры на C#.
                        • 0

                          Речь не о том, у вас, в статье опечатка и используется new Bar(); вместо new IBar();, о чем он и пытается вам сообщить.

                          • 0
                            IBar() — это интерфейс, а интерфейсы не инстанцируются. Эту свою ошибку и осознал gearbox :)
                            • 0

                              А, тогда извините.
                              Я, дяденька, не настоящий сварщик, просто это выглядело как-то, логично, что ли :)

                              • +1
                                Там вся соль в этом интерфейсе — именно благодаря ему мы делаем зависимым Bar от Foo. То есть Foo тоже зависит от IBar, но Foo и IBar контролируем мы, и всем кто хочет с нами общаться приходится соблюдать IBar. Именно на нем (в смысле с его появлением) и происходит инверсия.
                    • 0
                      Наверняка первый вопрос, который возник у вас при взгляде на заголовок, был «Шта?».

                      У меня он при взгляде на КДПВ возник.
                      • 0
                        public class Foo {
                          private IServer itsServer;
                        
                          public Foo(IServer server) {
                             itsServer = server;
                          }
                        }

                        У вас же получился типичный DI через конструктор, не?

                        • 0
                          Этот пример здесь уместен исключительно в контексте предшествующих примеров, где в конструкторах происходила инстантинация. Тут же наглядно видно, что инстантинация пропала из списка обязанностей класса Foo.
                          • 0

                            Во введении к статье вы написали буквально следующее:


                            А вы знали, что помимо внедрения зависимости через конструктор и сеттер, существует еще и третий способ — внедрение посредством интерфейса?

                            Общий стиль введения подразумевает, что читатель уже знаком с концепциями IoC и DI хотя бы путанно и в общих чертах, но хотя бы что-то. Кроме того, вы обязались рассказать про "еще один DI".


                            Далее шёл типичный рассказ про зависимости компонентов и, применив инверсию, вы получили обычное внедрение через конструктор. Внедрение же через интерфейс выглядит совсем по-другому.

                            • 0
                              Совершенно верно, поэтому я и написал, что это «выходит за рамки данной статьи». Два DI — это Dependency Inversion и Dependency Injection, два понятия, которые путают так же, как и Dependency Injection и Inversion of Control. Цель статьи — дать понимание, что мы используем Dependency Inversion, чтобы разделить модули асбтракцией, Dependency Injection, чтобы избавиться от инстантинации вручную, а реализуем это посредством фреймворка, построенного по принципу Inversion of Control. И ничто из этого не является синонимом друг друга.
                              • 0

                                Теперь понял, что вы имели в виду. В любом случае, описывать изначально в статье две сущности одной аббревиатурой не совсем красиво по отношению к читателю. Поэтому, кстати, "инверсия зависимостей" на самом деле "принцип инверсии зависимостей" (и SOLID есть набор принципов), потому его лучше бы писать явно как Dependency Inversion Principle, что сокращается как DIP, во избежание путаницы с Dependency Injection.

                                • +1
                                  В любом случае, описывать изначально в статье две сущности одной аббревиатурой не совсем красиво по отношению к читателю.

                                  Автор прямо указывает, что с терминологией в отрасли путаница, что и демонстрирует наглядно в статье :)
                                • 0
                                  Цель статьи — дать понимание, что мы используем Dependency Inversion, чтобы разделить модули асбтракцией, Dependency Injection, чтобы избавиться от инстантинации вручную, а реализуем это посредством фреймворка, построенного по принципу Inversion of Control. И ничто из этого не является синонимом друг друга.

                                  Вот это стоило написать в статье, а то лично я понял всё только после вашего комментария.
                                  И да, фраймворки, как правило, реализуют IoC по средствам Service Locator у себя в недрах.

                                  • +1
                                    Хорошее замечание, добавил это в подытоживающий вывод.
                                • +1
                                  Раньше не сталкивался с понятием interface injection, но некоторые IoC фреймворки поддерживают такую фичу как инъекция в метод. Получается что-то типа такого:
                                  public class Foo
                                  {
                                      IBar _bar;
                                      Qux _qux;
                                  
                                      [Inject]
                                      public Init(IBar bar, Qux qux)
                                      {
                                          _bar = bar;
                                          _qux = qux;
                                      }
                                  }
                                  

                                  В данном примере нет необходимости объявлять интерфейс, но приходится помечать метод атрибутом, чтобы фреймворк понял, куда требуется внедрить зависимости.
                                  • 0
                                    В данном случае это похоже на гибрид инъекции через конструктор и сеттер.
                                    • 0
                                      Такой подход удобен, например, в Unity3D, где нельзя (не рекомендуется) объявлять конструктор для классов, наследованных от MonoBehaviour. Приходится делать инъекцию в метод.
                                    • 0
                                      Если заменить Init на Foo, будет обычное внедрение через конструктор. Использовать отдельный метод для инициализации здесь и в общем случае излишне (кроме специальных случаев, подобных примеру с Unity3D ). В конструктор можно точно так же заинжектить интерфейс, как и в метод. То есть суть interface injection — только в том, что в метод/сеттер передается не реализация, а интерфейс. Таким образом, мы уходим от зависимости от конкретной реализации. Больше ничего.
                                      • 0
                                        Пример ухода от зависимости от класса путем перехода к зависимости от интерфейса из статьи Jakob Jenkov Understanding Dependencies (в моем переводе).
                                        Зависимость метода от класса:
                                        public byte[] readFileContents(String fileName){
                                            //open the file and return the contents as a byte array.
                                        }


                                        Зависимость метода от интерфейса:
                                        public byte[] readFileContents(CharSequence fileName){
                                            //open the file and return the contents as a byte array.
                                        }
                              • 0
                                Управление впрыском — задача определения необходимого объема впрыскиваемого форсунками инжекторного ДВС топлива.

                                Управление базируется (зависит) на данных с некоторых датчиков (ДМРВ, лямбды, положения заслонки, колен/распредвала, других).

                                «Инверсии зависимостей управления впрыском» — как при взгляде на название, так и на КДПВ, не подумал «Шта?». Подумал, что статья про альтернативный алгоритм управления форсунками. Может, про rusEfi наконец свежая статья. Может, про что другое. Но не «Шта?»
                                • 0
                                  Забавно, но «задача определения необходимого объема впрыскиваемых» зависимостей — это очень даже смежная тема :)
                                • +1
                                  Мне встречалась такая характеристика:
                                  > «Dependency Injection» is a 25-dollar term for a 5-cent concept.

                                  Действительно, термин на мой взгляд слишком запутан для простой концепции за ним.

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

                                  Самое читаемое