Использование IoC контейнеров. За и Против

    В свете выхода новой версии Enterprise Library, одной из важнейших частей которой является IoC-контейнер Unity, лично я ожидаю всплеск интереса к теме IoC(Inversion of Control) контейнеров, которые, по сути, являются реализацией достаточно известного паттерна Service Locator.

    В этом топике я хотел бы подискутировать на тему «правильного» использования таких контейнеров, а также предостеречь новичков от применения его «везде и всюду». Ну и просто интересующимся возможностями «новых технологий» тоже должно быть любопытно.

    Предыстория проблемы


    Обычно к знакомству с IoC-контейнерами подталкивает осознание необходимости следования принципам проектирования классов (SOLID), а именно, последний принцип, который говорит о том, что каждый класс должен зависеть не от конкретных объектов, а от абстракций(интерфейсов), и совсем замечательно, когда все эти зависимости объявляются в конструкторе.

    То есть код вида:
    class TextManager {
     public TextManager(ITextReader reader, ITextWriter writer) {
     }
    }


    * This source code was highlighted with Source Code Highlighter.

    это хорошо, а код вида:

    class TextManager {
     public TextManager(TextFromFileReader reader) {
     }

     public property DatabaseWriter Writer {
      set {
        _writer = value;
      }
     }
    }

    * This source code was highlighted with Source Code Highlighter.

    это плохо.

    При этом, если приложение/библиотека у вас большая, ITextWriter используется во многих классах, а какой именно Writer будет использоваться определяется на входе в библиотеку (допустим, у нас есть Writer'ы в БД и файл с общим интерфейсом), то логично возникает желание как-то связать ITextWriter с DatabaseWriter'ом где-то в одном месте.

    До SOLID-ов считалось вполне нормальным объявить внутри библиотеки в статическом классе переменную типа ITextWriter и хранить конкретный используемый на текущий момент Writer там. Но когда начинаешь задумываться о нормальной архитектуре… :) минусы статичных классов становятся очевидными — совершенно непрозрачно от чего именно каждый класс зависит, что ему нужно для работы, а что нет, и страшно даже представить, что «потянется» вместе с этим классом, если вдруг необходимо будет его перенести в другой проект или хотя бы другую библиотеку.

    IoC-контейнер


    Что же предлагается нам для решения проблемы? Решение давно придумано в виде IoC-контейнеров: мы «связываем» интерфейсы с конкретными классами, как только получаем необходимую информацию:

     var container = new UnityContainer();
     container.RegisterInstance<ITextWriter>(new DatabaseWriter());


    * This source code was highlighted with Source Code Highlighter.


    и имеем удобный способ создания объектов:
     container.Resolve<TextManager>();
    * This source code was highlighted with Source Code Highlighter.


    И если на примере одной зависимости, преимущество контейнеров неочевидно, то если представить, что конструкторы требуют 2-3 интерфейса, и таких классов у нас хотя бы 5-10, то о применении контейнеров покажется многим настоящим спасением.

    Когда контейнер — хорошо..


    Собственно, Unity и создавался для активного применения в сложных составных приложениях, с множеством реализаций идентичных интерфейсов и нелинейной логикой взаимозависимостей между реализациями. Говоря проще, если у нас на всю библиотеку не один-единственный интерфейс IWriter с двумя реализациями DbWriter и TextWriter, а еще, к примеру, IReader и ITextProcessor, для каждого из которых тоже существует 3-4 реализации, и TextWriter работает только с CsvReader'ом и ExcelReader'ом, а какой именно из ридеров надо использовать, зависит от конкретного типа текущего TextProcessor'а, ну и от фазы луны заодно.

    Очень сложно привести конкретные примеры кода, применение Unity в котором было бы, с моей точки зрения, обоснованно, и не загромоздить текст тонной ненужного кода. Но описанный «пример» создает некое подобие такой сложной и комплексной задачи :)

    А когда — не очень


    Применение конструкций типа container.Resolve(); кажется очень удобным, и поначалу так и тянет использовать его почаще, и инстанциировать классы таким образом везде, где это только возможно. Всё это влечет к пробрасыванию контейнера «по всей глубине» библиотеки/приложения, и организации доступа к IUnityContainer'у либо через конструкторы классов, либо, возвращаясь к прошлому, через статичные классы.
    class TextManager {
     public TextManager(IUnityContainer container) {
      _writer = container.Resolve<IWriter>();
      _reader = container.Resolve<IReader>();
     }
    }

    * This source code was highlighted with Source Code Highlighter.


    Это и является крайностью использования UnityContainer'а. Потому что:
    • наличие в конструкторе «непонятного» IUnityContainer скрывает пути его использования внутри класса, и постороннему человеку при повторном использовании вашего класса будет неясно, какие именно объекты/интерфейсы требуются классу для работы;
    • это усложняет Unit-тестирование, опять же потому, что непонятно, какие интерфейсы и операции замещать;
    • и, наконец, это прямое нарушение принципа Interface Segregation, говорящего об использовании интерфейсов без «лишних» методов (методов, не используемых в классе).


    ...Profit!


    Подытоживая, мне кажется, что использование IoC-контейнеров идеально именно в ситуациях со сложными переплетениями взаимозависимостей между конкретными реализациями, и только на самых верхних уровнях библиотеки/приложения.
    То есть сразу после точки входа в библиотеку следует регистрировать в Юнити реализации интерфейсов, и как можно раньше резолвить необходимые нам типы. Таким образом мы пользуемся всей мощью Юнити по упрощению процесса генерации сложнозависимых объектов, и в то же время следуем принципам хорошего дизайна и сводим к минимуму использование весьма абстрактного «контейнера» в нашем приложении, тем самым упрощая его тестирование.

    P.S. Поскольку сам в данной теме далеко не гуру, в комментариях буду рад услышать о собственных ошибках, а также о других возможных применениях IoC-контейнеров.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 80

      +4
      Если в коде встречается явное использование Resolve, то это неправильный пример применения контейнеров. Последний абзац похож на описание правильного подхода, но без конкретных примеров сложно оценить.
        0
        Я извиняюсь, но как можно ни разу явно не использовать Resolve в рамках не WPF проекта?
        В рамках Prism'a я это себе представляю, но в контексте невизуальных библиотек — не очень. Поясните, пожалуйста.
          0
          Для невизуальных библиотек, которые не являются частью большего проекта, DI framework (в отличии от самого принципа IoC) обычно не используют. Если библиотека является частью большего проекта с визуальной частью, можно включить в неё модуль регистрации, но контейнер ей не нужен, потому что все ссылки распознаются исключительно через конструкторы.
            0
            Я вот столкнулся буквально недавно с ситуацией, в которой на мой взгляд использование контейнера более чем оправдано — как раз за счет сложных взаимозависимостей.

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

            Спутанно объяснил, наверное :) Но проблема как раз в том, что объем кода значительный, и к простому примеру сложно свести…
              0
              Я делал очень сложные штуки, но в целом всё обычно сводится к одному-двум паттернам, которые легко реализуются через IoC.
              В любом случае, требеования на входе в том же Autofac можно легко описать как часть процесса регистрации.
      • НЛО прилетело и опубликовало эту надпись здесь
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Чем смутила? Что applicationContext.getBean(), что container.Resolve(), принцип тот же.
            • НЛО прилетело и опубликовало эту надпись здесь
            +1
            создавая объекты через .Resolve мы разрешаем зависимости дочерних классов. То есть дочерние классы у нас как раз inversion of control очень даже соблюдают по определению.

            Класс, из которого .Resolve вызывается — он тоже может быть очень даже IoC, а Юнити он использует внутри себя для удобного создания дочерних классов и разрешения их зависимостей.
            В данном случае это как бы «корень» приложения в терминологии Prism — точка, в которой становятся известны объекты, которые будут реализовывать интерфейсы.
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                Я совсем не в курсе Java, к сожалению…
                А как происходит инстанциирование класса Sample в первом случае?
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    ага, правильно
                    Тогда получается, что этот самый container таки должен пробрасываться до всех мест, где требуется инстанцировать новые объекты. Или я не прав?
                    Ну то есть на мой взгляд аналогия с юнити полная: хочешь инстанциировать через контейнер — пробрасывай контейнер.

                    А в топике я и акцентировал, что пробрасывание контейнера — не самая лучшая идея.

                    ну и по поводу @Inject-a проперти, я чуть ниже писал… что это, конечно, на вкус и цвет, но мне указание интерфейсов в конструкторе больше импонирует:

                    ILogger _logger;

                    // конструктор
                    public Sample(ILogger logger, IConnection connection, IFacade facade) {
                    _logger = logger;…
                    }
                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        Ну мы и пришли к тому же: что лучше контейнер использовать на начальном этапе, а в глубине использовать конкретные интерфейсы. Интерфейсы фабрик в частности.

                        Надеюсь, мы друг друга поняли :)
                        • НЛО прилетело и опубликовало эту надпись здесь
                            0
                            В самом начале:

                            «То есть код вида:
                            class TextManager {
                            public TextManager(ITextReader reader, ITextWriter writer) {
                            }
                            }
                            это хорошо»

                            А участок с пробросом контейнера идет под подзаголовком «А когда — не очень» как пример не самого лучшего приема
                            • НЛО прилетело и опубликовало эту надпись здесь
                              • НЛО прилетело и опубликовало эту надпись здесь
            0
            Позволю с Вами несогласится по поводу второго варианта (там, где объявляется property). Внедрение зависимостей таким образом тоже может осуществляться.
              +1
              Оно может, конечно. И синтаксис для этого есть в Юнити…
              Но я потому и написал, что это «совсем замечательно», когда через конструктор. Это да, личное мнение. На мой взгляд, в большинстве случаев инициализации зависимостей через проперти стоит избегать. Причина — та же самая, непонятно, когда это проперти инициализировать необходимо, а когда — нет.
                0
                У контструктор инжекшен и проперти инжекшен просто разные юзкейсы. Конструткор инжекшен используют когда зависимость 100% нужна. Проперти — когда ею можно пренибречь. Например логгер может быть не обязательной зависимостью. В таком случае его делають пропертью.
              0
              Всем интересующимся советую посмотреть на RoboContainer.
                0
                Но даже при xml-конфигурировании библиотеки, в которых реализуются интерфейсы, должны быть статически слинкованы с исходной библиотекой (которая использует RoboContainer)?
                Или контейнер умеет динамически подгружать зависимости?
                +2
                Как уже товарищи писали все это неудобно из-за отсутствия аннотаций. Посмотрите как это сделано в Spring. Spring опять же существует и под .NET.
                  0
                  1) Анотации(атрибуты) есть, если есть желание, или надо что-то уточнить.
                  2) Мы свято уверены, что анотации в этом случае это зло, так как
                  а) тянут зависимость на конкретную реализацию контейнера.
                  б) это дополнительный код, кторый можно писать. Если контейнер может сам разрулить… Вперед. Пусть сам разруливает.
                    +1
                    Анотации(атрибуты) есть, если есть желание, или надо что-то уточнить.

                    Autowired и объявления Repository и Service к примеру. Очень сильно облегчает жизнь. Да в Spring можно без аннотаций как приведено у вас, но это вызывает очень много не нужной писанины. Аннотации от этого избавляют.

                    Мы свято уверены, что анотации в этом случае это зло, так как
                    а) тянут зависимость на конкретную реализацию контейнера.

                    Эм. А что ваша текущая реализация неконтейнерозависима?

                    б) это дополнительный код, кторый можно писать. Если контейнер может сам разрулить… Вперед. Пусть сам разруливает.

                    Основная идея аннотаций в том чтобы писать меньше кода и разруливанием занимался контейнер. Иначе зачем он нужен?
                      0
                      Судя по всему у нас получилось небольшое недоразумение. Вы под анотациями что подразумеваете?

                      Если я вас правильно понял, то вы подразумеваете что анотация это что-то что вешается на клас или на интерфейс и указывает где искать соотвевенно интерфейс или реализацию? Если это так то у нас этот подход не прижился. Это делается более гибким способом, просто говорится что для такого-то интерфейса реализации лежат там-то. Или у теаких то реализаций, интерфейсовм считать первый. Для этого всего есть флуент интерфейсы. То что вам надо, пишется в две стороки. Но реально, оно просто никому не нужно. Проще для всего проекта использовать конвеншены, чем анотировать каждый клас-интерфейс.

                      Эм. А что ваша текущая реализация неконтейнерозависима?


                      Я не понял, вашего вопроса, но проиллюстрирую свою мысль. Аннотация это часть IoC фреймворка. Значит, как только вы повесили эту аннотацию, вы получили зависимость на фреймворк.

                      Основная идея аннотаций в том чтобы писать меньше кода и разруливанием занимался контейнер. Иначе зачем он нужен?


                      Наверное, вы должны привести примерчик. Потому как мы пишем без аннотаций, и у нас контейнер все разруливает сам.
                        +2
                        Если я вас правильно понял, то вы подразумеваете что анотация это что-то что вешается на клас или на интерфейс и указывает где искать соотвевенно интерфейс или реализацию? Если это так то у нас этот подход не прижился.

                        Нет. Оно указывает что тут надо подсунуть конкретный экземпляр. К примеру в классе объявляем:
                        @Autowired
                        ClientDao dao;

                        А в классе имплементации интерфейса пишем:
                        @Repository
                        JpaClientDao implements ClientDao

                        Дальше уже не моя головная боль как это будет связано, это решает сам фреймворк. Т.е. мне надо только инициализировать контейнер и указать что используются аннотации. Далее уже я делаю getInstance только для тех объектов что я буду использовать. Если они используют какой либо ORM или им необходимо инициализировать какие либо значения это делает сам фреймворк.

                        Я не понял, вашего вопроса, но проиллюстрирую свою мысль. Аннотация это часть IoC фреймворка. Значит, как только вы повесили эту аннотацию, вы получили зависимость на фреймворк.

                        Я вообще-то намекаю что вы в любом случае используя IoC зависите от используемого фреймворка. Если вы его поменяете вам прийдется переписывать код.

                        Наверное, вы должны привести примерчик. Потому как мы пишем без аннотаций, и у нас контейнер все разруливает сам.

                        Смотрите выше.
                          –2
                          Я вас правильно понял. Это все делается без аннотаций. Одним конвеншеном.

                          container.BasedOn<IRepository>();
                          


                          Что обозначает, зарегестрировать все репозитарии(то что наследуется от IRepository, тоесть IOrderRepository, IProductRepository) и их реализации (NHibOrderRepository, NHibProductRepository). Заместо базового интерфейса я могу использовать аннотацию, а могу и вообще по имени(точнее по суфиксу). Это и есть конвеншены. Тоесть мы в команде договриваемся что будет обозначать что это компонент. Мы не завязаны на конкретные атрибуты (хоть и можем их использовать). И таким макаром можно зарегестрировать все. И никаких анотаций в каждом класе ;).

                          Я вообще-то намекаю что вы в любом случае используя IoC зависите от используемого фреймворка

                          А я вам пытаюсь обьяснить что это не так. Есть три компонента. Контракты(интерфейсы), Реализации и собсно Приложение. Так вот первых два компонента, вообще невкурсе что существуют IoC контейнеры. Про IoC знает только Приложение. Да и то в достаточно ограниченной форме. Все знания Приложения о IoC контенере заканчиваются на фактори класах рутовых обьектов(как пример контроллеры в веб приложении, или презентеры в десктопном).
                          • НЛО прилетело и опубликовало эту надпись здесь
                              0
                              Дада. Именно про это. Пока собственно инжектить надо мало, можно и в ручную, но когда инжектить надо много то аннотации рулят.
                              0
                              container.BasedOn();

                              Что обозначает, зарегестрировать все репозитарии(то что наследуется от IRepository, тоесть IOrderRepository, IProductRepository) и их реализации (NHibOrderRepository, NHibProductRepository). Заместо базового интерфейса я могу использовать аннотацию, а могу и вообще по имени(точнее по суфиксу). Это и есть конвеншены. Тоесть мы в команде договриваемся что будет обозначать что это компонент. Мы не завязаны на конкретные атрибуты (хоть и можем их использовать). И таким макаром можно зарегестрировать все. И никаких анотаций в каждом класе ;).

                              Вам прийдется в обязательном порядке наследоваться от IRepository или эе писать на каждый интерфейс по такой записи. Опять же не совсем понятно как это дальше будет использоваться. Как у вас написано выше спросить у контейнера какой instance будет я правильно понял?

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

                              А в случае использования аннотаций интерфейсам и реализациям надо знать про фреймворк. Да действительно это так, но в противном случае вам прийдется или прийдется инжектинг прописывать в конфигуации или в приложении. Что будет способствовать написанию кучи конфигурационного кода. Который опять же прийдется переписывать если вы будете изменять фреймворк. Единственный плюс возникает только при написании нескольких приложений с разными фреймворками. Но в реальности такое бывает редко.
                              0
                              Разве @Autowired, @Repository не являются своего рода аннотациями (читай фреймворкоспецифичные вещи)?

                              Я вообще-то намекаю что вы в любом случае используя IoC зависите от используемого фреймворка. Если вы его поменяете вам прийдется переписывать код


                              Если не использовать аннотации/аттрибуты, то базовый код не имеет зависимости от фреймворка. Её будет иметь только прикладной код, которому необходимо каким-то образом настроить зависимости в коде ниже перед использованием.

                              Да и проблематично аттрибутами динамически менять зависимости...(интересно как это в .NET сделать?)

                                +1
                                Разве @Autowired, @Repository не являются своего рода аннотациями (читай фреймворкоспецифичные вещи)?

                                Являются.

                                Если не использовать аннотации/аттрибуты, то базовый код не имеет зависимости от фреймворка. Её будет иметь только прикладной код, которому необходимо каким-то образом настроить зависимости в коде ниже перед использованием.

                                Конечно. Но с моей точки зрения лучше использовать аннотации, чем долго и муторно описывать конфигурацию. Это все как раз придумывали чтобы не прописывать долго и муторно конфиги. Осталось дождаться пока такого рода аннотации станут стандартом и можно будет безболезненно менять фреймворки. К примеру в случае с JPA это можно делать как со стороны фреймворка, так и со стороны ORM :)
                                • НЛО прилетело и опубликовало эту надпись здесь
                                    0
                                    О. Упустил из виду :)
                        0
                        Сам пользовался SCSF (предшественник Prism), так вот там все рулится только на основе атрибутов (в т.ч. при объявлении зависимостей в конструкторе необходимо помечать параметры атрибутами [ServiceDependency]).

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

                        А вот если аналогично объявлять такие зависимости на более низком уровне не в конструкторе, а в виде публичных свойств, то велика вероятность получить какой-нибудь ServiceNotFoundException в runtime, после чего приходится лезть в CallStack и искать причину, какой же сервис оказался недоступен.

                        Приведенный в статье пример с публичным сеттером как раз иллюстрирует данную проблему — неочевидно, от каких «сервисов» зависит объект.

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

                        Короче, на мой взгляд, подход Prism как раз избавляет от таких проблем (или способствует этому), и я этому рад.
                        +4
                        Смешались в кучу люди, кони… IoC контейнер — это не реализация паттерна ServiceLocator, IoC контейнер — это фреймворк сделаннй для обобщения реализации принципа, сюрприз, IoC (он же DI, по терминологии фаулера). А SL — это совершенно отдельная конструкция, практически прямо противоположная, у того же фаулера все это достаточно подробно, хотя и мутно расписано.
                        Ну и всплеска ждать не стоит, куда уж больше, сейчас уже только ленивый свой контейнер не написал… =))

                          –1
                          IoC — принцип, SL — паттерн. Точно также Фаулер не мешает в кучу IoC и DI. DI — одна из конкретных реализаций принципа IoC. Но IoC — это не обязательно DI.

                          Тем более нельзя говорить о «противоположности» SL и DI. Они разные в принципе, но не противоположные.
                            0
                            Верно, фаулер не мешает, фаулер вводит свою терминологию, более точно отражающую суть вещей. Что такое IoC точного ответа дать не может никто и внятной формулировки не существует, поэтому фаулер вводит DI, четко описывая что это означает. Поэтому да, в каком-то смысле DI можно считать вариантом IoC или «принципа голливуда».

                            Не верно. О противоположности DI и SL говорить не только можно, но и нужно. DI инжектит зависимость снаружи, в SL она разруливается изнутри, это принципиальная разница не смотря на то, что DI формально считается «принципом», а SL «паттерном», что тоже предмет для дискуссии.
                          0
                          Dependency Injection и Service Locator — диаметрально противоположные шаблоны, решающие одну и ту же задачу нахождения зависимостей. Однако Service Locator плох тем, что добавляет объекту еще одну зависимость: от реестра (контейнера) сервисов.

                          У Вас описано использование именно ServiceLocator.

                          PS. Кстати, зависимость от контейнера это анти-паттерн :-)
                            0
                            Ну Service Locator снимает зависимость от конкретного сервиса, так что, это вопрос задачи и архитектуры — что именно и когда применить.
                            0
                            «IoC (Inversion of Control) контейнеров, которые, по сути, являются реализацией достаточно известного паттерна Service Locator»

                            Что означает эта фраза???

                            Service Locator — один из способов инверсии зависимости (IoC), ровно как и Dependency Injection (DI). SL и IoC — не синонимы, разбирайтесь в сабже более детально.
                            • НЛО прилетело и опубликовало эту надпись здесь
                                0
                                Да, thanks за замечание, не приметил.
                                0
                                Service Locator — один из способов инверсии зависимости (IoC)

                                неа, ServiceLocator это прямой способ поиска зависимости и контролируется самим классом. IoC инвертирует поиск зависимости, т.е сам «ServiceLocator» вставляет зависимости (DI) в объект.
                                  +1
                                  С одной стороны — согласен.
                                  Но с другой — container.Resolve разве не предоставляет нам возможность прямого поиска зависимостей?

                                  Другой вопрос, что такое использование юнити (как ServiceLocator'а) в большинстве случаев не оправдано — это да.
                                    0
                                    «такое использование юнити (как ServiceLocator'а) в большинстве случаев не оправдано»

                                    Exactly!
                                      0
                                      Просто Unity реализует оба паттерна.
                                      0
                                      Скажем так, SL — один из паттернов, позволяющий избежать прямой зависимости между объектами, а DI — несколько способов собственно инъекции. Мне так кажется, эти вещи несколько из разных измерений, чтобы их напрямую сравнивать (хотя сделать это можно), но вот, например, Фаулер их аналогично разделяет.

                                      ServiceLocator, думаю ничто никуда не «вставляет», это паттерн, набор принципов, которые, как мне кажется, можно реализовать несколькими способами. В двух словах — можно использовать SL без DI.
                                        0
                                        Я не зря написал «ServiceLocator» в кавычках. Имелся ввиду объект — реестр сервисов.
                                          0
                                          DI с SL мешать в месте — вообще странная затея. Это в принципе два разных подхода к организации кода.
                                        0
                                        IoC-контейнер является и реализацией паттерна Service Locator в том числе, разве нет?

                                          +2
                                          Ну вот как раз вовсе не обязательно именно SL (ну, или не только SL). В частности Unity, о котором идет речь.
                                            0
                                            Нет. IoC-контейнер, как правило, является реализацией DI. SL-и тоже есть, но это отдельная песня.
                                            Вся чехарда, на самом деле, из-за того, что четкого определения IoC нету, но традиционно, начиная с Pico-контейнера и Spring-а, под IoC понималось то, что мы сейчас называем DI, всед за фаулером. Поэтому подавляющее большинство IoC контейнеров являются DI контейнерами, то есть, они оборудованы механизмами инжекции зависимости в объект снаружи и их основная задача эту инжекцию обобщить и сделать прозрачной для разработчика.То что мы можем коде сказать Container.Resolve — ничего не меняет.

                                            SL работает по совершенно противоположному принципу, вообще, правильный SL требует реализации некоторых интерфейсов в самих сервисах. Помните ISite, IContainer, в классах поддерживаемых дизайнером VS, и отдельно IServiceProvider? Это вот оно, данная реализация SL называется IServiceProvider, на нем вообще вся Visual Studio построена.
                                          0
                                          Пишу на жаве и использую Guice. Никаких проблем, перечисленных вами, нету. Видимо это проблемы реализации, а не всех IoC.

                                          >наличие в конструкторе «непонятного» IUnityContainer скрывает пути его использования внутри класса, и постороннему человеку при повторном использовании вашего класса будет неясно, какие именно объекты/интерфейсы требуются классу для работы;

                                          У нас @Inject и все хорошо.

                                          >это усложняет Unit-тестирование, опять же потому, что непонятно, какие интерфейсы и операции замещать;

                                          А у нас это упрощяет Unit-тестирование.

                                          >и, наконец, это прямое нарушение принципа Interface Segregation, говорящего об использовании интерфейсов без «лишних» методов (методов, не используемых в классе).

                                          Непонял. Вообще создавать интерфейс на каждый класс это бред. У меня в 90% случаев зависимости тянуться не в виде интерфейсов а в виде классов.
                                            0
                                            Вы не внимательно читали. Пример с «непонятного» IUnityContainer это один из вариантов, который собсно автору не очень то и нравится. Автору нравится первый пример кода. Без всяких шаманств с атрибутами типа @Inject. И это полностью потдерживается Unity.

                                            >>это усложняет Unit-тестирование, опять же потому, что непонятно, какие интерфейсы и операции замещать;

                                            >А у нас это упрощяет Unit-тестирование.

                                            Это и есть один из пунктов почему автору не нравится способ с «непонятного» IUnityContainer… Когда используется первый вариант, то все нормально с тестированием.
                                              +1
                                              > У нас @Inject и все хорошо.
                                              Inject — это типа атрибутов нетовских насколько я понимаю? Тогда ты прибил конкретную реализацию IoC-контейнера к своим классам.

                                              >А у нас это упрощяет Unit-тестирование.
                                              каким образом?

                                              class TextManager1 {
                                              public TextManager(IUnityContainer container) {
                                              _writer = container.Resolve();
                                              _reader = container.Resolve();
                                              }
                                              }

                                              class TextManager2 {
                                              public TextManager(IWriter writer, IReader reader) {
                                              _writer = writer;
                                              _reader = writer;
                                              }
                                              }

                                              по моему достаточно очевидно, что TextManager2 в разы проще протестировать, чем TextManager1
                                                0
                                                Да, @Inject это атрибуты, точнее то что в .NET называется атрибутами, а в java аннотации.
                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                    0
                                                    У меня это выглядело бы так:

                                                    class TextManager {
                                                    @Inject
                                                    public TextManager(IWriter writer, IReader reader) {
                                                    _writer = writer;
                                                    _reader = writer;
                                                    }

                                                    А в тестах есть 2 варианта — подставить в конструктор реализации читателя и писателя руками, либо создать тестовый контекст.
                                                      0
                                                      ну так прокомментировал ты другой код, в котором в конструктор передается IoC контейнер. А из такого кода не видно, реализую читателя/писателя нужно подставлять или реализацию IComponent/IList
                                                    0
                                                    > А у нас это упрощяет Unit-тестирование.

                                                    Мы чуть выше дискутировали по этому вопросу. У вас же не пробрасывается context в глубину классов?
                                                    В этом пункте в посте я описывал нежелательную реализацию, когда в конструкторе класса (ну или в виде @Inject Context context;) есть зависимость от IUnityContainer'a с целью .Resolve объектов внутри класса.

                                                    >Вообще создавать интерфейс на каждый класс это бред.
                                                    Это очень зависит от конкретной задачи. Конечно, если класс простой с единственной ответственностью и исключено создание другой реализации этой же ответственности — то это бред.

                                                    А в некоторых случаях практически каждая операция легко сводится к интерфейсу, а для разных входных объектов выполнять эти операции надо по-разному. Вот у меня в текущем проекте и получается, что в 90% случаев класс реализует интерфейс. И на каждый интерфейс по 2 класса минимум.
                                                      0
                                                      >Вот у меня в текущем проекте и получается, что в 90% случаев класс реализует интерфейс. И на каждый интерфейс по 2 класса минимум.

                                                      А не поделитесь, что за задача? Правда интересно. У меня как-то очень редко требуется несколько реализаций. (Моя область это REST-сервисы, работающие с БД через ORM)
                                                        0
                                                        Работа с медицинскими приборами. Конкретно на данный момент — импорт данных из них.
                                                        На выходе данные в строго определенном формате, а на входе — множество приборов, которые внутри содержат, в общем и целом, одни и те же данные, но либо в разных форматах, либо запрашивать их приходится разными способами.

                                                        В итоге имеем несколько интерфейсов «коммуникационного» уровня, для импорта различных типов данных из разных устройств, интерфейсы для конвертации в идентичный низкоуровневый «промежуточный» формат данных для групп приборов, и конвертация в итоговый выходной формат. Это если «грубо».

                                                        Чем выше уровнем, тем число реализаций каждого интерфейса, конечно, уменьшается. Но и даже там, где сейчас всего одна реализация интерфейса, при добавлении поддержки доп. прибора легко появится еще одна, поэтому приходится предусматривать.
                                                    0
                                                    Я один не понимаю почему не упоминается DependencyAttribute, который и устраняет псевдо-недостаток?
                                                      0
                                                      Invertion of Control — это принцип, который позволяет нам инвертировать зависимости, т.е. отвязать контракт и имплементацию.
                                                      В данном контексте (прикладного инструментария) он реализуется в двух видах (паттернах):
                                                      Service Locator — из перевода нетрудно догадаться, что в данном случае мы знаем, где искать сервисы, о чём и сообщаем с помощью: ServiceLocator.GetDependency Injection — так же из перевода нетрудно понять, что в данном случае смысл в инъекции зависимости, в конструктор, свойства и т.д. Прелесть DI в том, что обычно вы не превязаны ни к какой инфраструктуре, если конечно не используете, что-нибудь на подобии зависимых от фреймворка аннотаций ([Inject] @Inject)

                                                      DI либо SL по-раздельности использовать на практике не получается, поэтому оба принципа комбинируются.
                                                      0
                                                      если не гуру, зачем вообще браться писать статьи?
                                                        +4
                                                        в спорах рождается истина(с)
                                                        +2
                                                        Глянул вскользь дискуссию. В принципе, у меня 50%й соблазн написать пост а-ля «как использовать Unity», т.к. тема всяких применений явно не раскрыта. Кстати, я тут же, на Хабре, публиковал серию про Unity, но она носила чисто обучающий характер. Фактическое использование в production — это несколько другая задача.
                                                          0
                                                          пиши, было бы любопытно именно в продакшн.
                                                            0
                                                            Однозначно нужен пост поверх этого. Комменты тему не раскрыли, как это порой бывает.
                                                            0
                                                            Написал пробный пост на тему in-production использования Unity. В зависимости от реакции возможно напишу еще.
                                                              0
                                                              наличие в конструкторе «непонятного» IUnityContainer

                                                              При использовании активной инжекции зависимостей сам контейнер будет фигурировать только на старте приложения, никаких ссылок в проекте на него не надо.
                                                                0
                                                                Вы используете внедрение зависимостей правильно, если:

                                                                1. Вызов метода IUnityContainer.Resolve делается только один раз в начале работы приложения для построения всего графа рабочих объектов!

                                                                2. В случае когда вам по ходу работы приложения нужно создавать конкретные объекты, вы должны использовать не контейнер, который к тому моменту уже прекратил своё существование, а фабрики объектов! Фабрики, в свою очередь, являются частью графа рабочих объектов приложения.

                                                                3. Рабочие объекты приложения общаются друг с другом через интерфейсы.

                                                                Соблюдение этих трёх принципов откроет вам преимущества механизма внедрения зависимостей, особенно при написании модульных тестов.

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

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