Pull to refresh

Comments 51

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

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

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

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

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

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

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

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

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

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

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

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

Если совсем вкратце, то можно вот тут почитать — http://dimakudr.blogspot.ru/2010/07/blog-post.html (плюс там еще вторая часть есть).
UFO just landed and posted this here
Каждый получатель, несмотря на то, что получает объект IPlayer, вынужден указывать, объект какого типа (чтиай: какого класса) он хочет получить

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

Суть в том, что получателю все равно какую конкретную реализацию ему дадут, он сможет работать с любой.
UFO just landed and posted this here
Если данные идеологически разные, то зачем их в один интерфейс пытаться объединить?

А если данные одинаковы, но просто разные способы получения этих данных, допустим через разные каналы связи, то потребителю как раз не важно, как именно получаются данные, он знает, что предоставленный ему объект имеет интерфейс для получения этих данных.
UFO just landed and posted this here
О, в случае этой книги это совершенно не принципиально.
В таком случае, как мне кажется, механизм «фоновой» инстантинации, которую предлагает DI, вам просто не подходит. Наличие DI-фреймворка в системе не означает же, что вообще ВСЕ зависимости надо прогонять через него. В вашем инструментарии всегда есть те же фабрики и фабричные методы, если нужно инкапсулировать инстантинацию в одном месте, но при этом клиентский код должен ее контролировать.
UFO just landed and posted this here
Тогда, когда можно, грубо говоря, «один раз сконфигурировать и забыть», избавившись таким образом от написания оператора new или вызовов Service Locator'а каждый раз, когда вам нужен экземпляр класса, на который указывает зависимость. Здесь, скорее, стоит вопрос, когда надо использовать инверсию зависимостей, а когда нет, а это отдельная большая тема для дискуссий.
Использование DI предполагает, что вашем клиенту все равно какая реализация IPlayer будет ему предоставлена. Классический пример, это тестирование — например, вам не нужно чтобы тест слал статистику по игрокув Windows Store, и вы поставляете класс реализующий IPlayer, но не взаимодействущий с сервисом Windows Store.
Представьте себе ваше приложение, разделите его условно на слои. Например слой доступа к данным, бизнес логика, пользовательский интерфейс. Теперь когда вы провели грань, попробуйте их описать так, что бы каждый слой был максимально независим от другого. IoC это собственно и есть способ реализовать ваши слои независимыми друг от друга. Dependency Injection и Service Locator являются способами реализации самого контейнера. При этом Service Locator считается анти-паттерном, так как ведёт к тому что наши слои будут зависимы от конкретного контейнера, что в принципе немного не укладывается в саму суть IoC контейнера. Хотелось бы увидеть побольше статей на эту тему, так как дискутировать можно очень долго
IoC это собственно и есть способ реализовать ваши слои независимыми друг от друга

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

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

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

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

Читать как

public class Foo {
  private IBar itsBar;

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

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

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

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

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

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

У меня он при взгляде на КДПВ возник.
UFO just landed and posted this here
Этот пример здесь уместен исключительно в контексте предшествующих примеров, где в конструкторах происходила инстантинация. Тут же наглядно видно, что инстантинация пропала из списка обязанностей класса Foo.
UFO just landed and posted this here
Совершенно верно, поэтому я и написал, что это «выходит за рамки данной статьи». Два DI — это Dependency Inversion и Dependency Injection, два понятия, которые путают так же, как и Dependency Injection и Inversion of Control. Цель статьи — дать понимание, что мы используем Dependency Inversion, чтобы разделить модули асбтракцией, Dependency Injection, чтобы избавиться от инстантинации вручную, а реализуем это посредством фреймворка, построенного по принципу Inversion of Control. И ничто из этого не является синонимом друг друга.
UFO just landed and posted this here
В любом случае, описывать изначально в статье две сущности одной аббревиатурой не совсем красиво по отношению к читателю.

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

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

Хорошее замечание, добавил это в подытоживающий вывод.
Раньше не сталкивался с понятием interface injection, но некоторые IoC фреймворки поддерживают такую фичу как инъекция в метод. Получается что-то типа такого:
public class Foo
{
    IBar _bar;
    Qux _qux;

    [Inject]
    public Init(IBar bar, Qux qux)
    {
        _bar = bar;
        _qux = qux;
    }
}

В данном примере нет необходимости объявлять интерфейс, но приходится помечать метод атрибутом, чтобы фреймворк понял, куда требуется внедрить зависимости.
В данном случае это похоже на гибрид инъекции через конструктор и сеттер.
Такой подход удобен, например, в Unity3D, где нельзя (не рекомендуется) объявлять конструктор для классов, наследованных от MonoBehaviour. Приходится делать инъекцию в метод.
Если заменить Init на Foo, будет обычное внедрение через конструктор. Использовать отдельный метод для инициализации здесь и в общем случае излишне (кроме специальных случаев, подобных примеру с Unity3D ). В конструктор можно точно так же заинжектить интерфейс, как и в метод. То есть суть interface injection — только в том, что в метод/сеттер передается не реализация, а интерфейс. Таким образом, мы уходим от зависимости от конкретной реализации. Больше ничего.
Пример ухода от зависимости от класса путем перехода к зависимости от интерфейса из статьи 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.
}
Управление впрыском — задача определения необходимого объема впрыскиваемого форсунками инжекторного ДВС топлива.

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

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

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

Articles

Change theme settings