.Net IOC containers
Теория: Learn the purpose (what is solved, how it looks without them, what is the difference from Service Locator, Singleton).
IOC - принцип объектно-ориентированного программирования, который используется для уменьшения связности в компьютерных программах (каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.).
Контейнер IoC (Inversion of Control) - мощное средство для управления зависимостями классов. Внедрение зависимостей - это способ исключения hardcoded взаимосвязей классов. Вместо этого зависимости определяются во время выполнения, что даёт бОльшую гибкость благодаря тому, что они могут быть легко изменены.
Смысл: модули (классы) верхнего уровня не должны зависеть от модулей нижнего уровня.
Например: Если есть компонент в приложении, который отвечает за отправку смс, то он не должен зависеть от конкретного провайдера. Вместо этого передается ссылка (интерфейс / абстракция) по принципу Dependency Injection. Возможно передавать объекты через сеттеры.
IoC контейнер — это нечто вроде справочника, который знает какие объекты и с какой реализацией нужно отдавать при запросе какого-либо класса.
Например: классу требуется класс User, вместо того чтобы передавать сам объект User / создавать его внутри своего компонента, через DI можно передать интерфейс UserInterface, а IoC контейнеру сообщить, что, когда потребуется класс UserInterface — отдай класс User.
Откуда выходит проблема:
Каждый класс должен зависеть не от конкретных объектов, а от абстракций (интерфейсов) - DI.
Как делали раньше:
Считалось нормальным объявить внутри библиотеки в статическом классе переменную типа ITextWriter и хранить конкретный используемый на текущий момент Writer там.
Проблема
Совершенно непрозрачно от чего именно каждый класс зависит, что ему нужно для работы, а что нет, и страшно представить, что «потянется» вместе с этим классом, если вдруг необходимо будет его перенести в другой проект или другую библиотеку.
Регистрация контейнера (на примере Unity):
Способ создания объектов:
Когда нужно использовать:
Применение в сложных составных приложениях, с множеством реализаций идентичных интерфейсов и нелинейной логикой взаимозависимостей между реализациями.
Например, если на всю библиотеку интерфейс IWriter с реализациями DbWriter и TextWriter, IReader и ITextProcessor, для каждого из которых существует 3-4 реализации, и TextWriter работает только с CsvReader'ом и ExcelReader'ом, а какой именно из ридеров надо использовать, зависит от конкретного типа текущего TextProcessor'а.
Когда не следует использовать:
Применение конструкций типа container.Resolve() влечет к пробрасыванию контейнера «по всей глубине» библиотеки /приложения, и организации доступа к контейнеру либо через конструкторы классов, либо, возвращаясь к прошлому, через статичные классы.
• наличие в конструкторе «непонятного» контейнера скрывает пути его использования внутри класса, и постороннему человеку при повторном использовании класса будет неясно, какие именно объекты/интерфейсы требуются ему для работы;
• усложнение Unit-тестирования, потому, что непонятно, какие интерфейсы и операции замещать;
• нарушение принципа Interface Segregation, говорящего об использовании интерфейсов без «лишних» методов (методов, не используемых в классе).
Как работает:
При запросе объекта типа у контейнера он решает объект какого типа вернуть. Для каждого типа, зарегистрированного в IoC-контейнере, есть карта зависимостей, то есть описание какие параметры надо передавать в конструктор, каким свойствам надо присваивать и какие методы вызывать чтобы инъектировать зависимости в объект.
Карта зависимостей задается внешне или получается с помощью рефлексии.
Кроме того контейнер содержит ассоциации для какого запрошенного идентификатора объект какого типа надо вернуть. В качестве идентификатора чаще всего используется сам тип. Для каждой зависимости запрошенного объекта, контейнер создает другой объект у которого тоже могут быть зависимости, для них эта операция вызывается рекурсивно.
В итоге:
Использование IoC-контейнеров идеально именно в ситуациях со сложными переплетениями взаимозависимостей между конкретными реализациями, и только на самых верхних уровнях библиотеки/приложения.
То есть сразу после точки входа в библиотеку следует регистрировать в Юнити реализации интерфейсов, и как можно раньше резолвить необходимые нам типы. Таким образом мы пользуемся всей мощью Юнити по упрощению процесса генерации сложнозависимых объектов, и в то же время следуем принципам хорошего дизайна и сводим к минимуму использование весьма абстрактного «контейнера» в нашем приложении, тем самым упрощая его тестирование.
Серега:
IOC контейнер - структура в коде, делает:
помогаети реализовывать inversion of control, (опосредованные зависимости через интерфейсы) - мы инвертируем зависимости в любом случае, вопрос в получении этих зависимостей: либо делаем все руками, сами делаем объект и берем все зависимости, либо IOC берет и получает все зависимости для этого объекта
Либо , например, есть абстрактная фабрика - шаблон, просто определяет что по интерфейсу отдает конкретный инстанс. (менее гибкая)
Есть IOC контейнер и пользователей, спрашивает у IOC дайте мне провайдер, он автоматически из списка возможных провайдеров по списку зависимостей (раньше ему объяснили как на это смотреть), выбирает и отдает нужный. Есть потрибетель IOC и есть конкретно человек, который объясняет IOC как работать (регистровать новые инстансы и депенденси какие нужны)
1) дает объект какого то интерфеса (IOC) - разбиение зависимости
2) создание и управление жизненным циклом таких зависимостей
Serivce locator - умеет решать обе задачки
Singleton - только вторую
Например, есть logger - конкретный инстанс, и он постороен по опред интерфейсу, IOC знает о них двоих, о нем никто. В случае нужных зависимостей , инстанс знает об интерфейсе, а вот о реализации знает IOC
Service locator (чаще всего синглтон) - кусок кода, внутри написано, создайте мне логировщик, но при этом он, в отличие от IOC контейнера не передает все нужные зависимости при создании этого объекта. Уже сам объект спрашивает у локатора об этих самых зависимостях. Поэтому этот логировщик будет работать в рамках сервис локатора.
Service Locator
Смысл:
Имеем фабричный метод который по идентификатору возвращает нужную реализацию интерфейса.
Singleton
Смысл:
Прогресс на 08/16/18