Работа с IoC-контейнером в Spring
В этой статье рассказывается о шаблонах проектирования, на которых строится работа со Spring-контейнером и о том, каким классом он представлен во фреймворке Spring.
Мы создадим простейший пример, в котором конфигурируются бины, создается контейнер и из него извлекаются готовые бины.
Инверсия управления (Inversion of Control) – что это
Ключевая особенность приложения, написанного на Spring, состоит в том что большую часть объектов создаем не мы, а Spring. Мы лишь конфигурируем классы (с помощью аннотаций либо в конфигурационном XML), чтобы «объяснить» фреймворку Spring, какие именно объекты он должен создать за нас, и полями каких объектов их сделать. Spring управляет созданием объектов и потому его контейнер называется IoC-контейнер. IoC расшифровывается как Inversion of Control. А объекты, которые создаются контейнером и находятся под его управлением, называются бинами.
Иллюстрировать это можно так:
В общем на вход контейнер Spring принимает:
А на выходе он производит объекты – бины. То есть экземпляры классов, созданные в соответствии с конфигурацией и внедренные куда нужно (в другие бины). После этого никакие операторы new нам не понадобятся, мы будем работать в классе-бине с его полями-бинами так, будто они уже инициированы. Конечно, не со всеми полями, а только с теми, которые сконфигурированы как бины. Остальные инициализируются как обычно, в том числе с помощью оператора new.
Что такое внедрение зависимости (Dependency Injection)
Внедрение зависимости — это и есть инициализация полей бинов другими бинами (зависимостями).
Ведь помимо создания объектов, Spring-контейнер внедряет эти объекты в другие объекты, то есть делает их полями других объектов. Иногда это выглядит магически – например, контейнер способен внедрить зависимость в поле с модификатором private, для которого нет сеттера. Как же код Spring может это сделать? Дело в том, что под капотом он использует рефлексию, так что это реально. Но эти детали для нас как разработчиков не важны, главное знать, как объяснить фреймворку, какие объекты вы хотите отдать под его управление, и в какие поля других объектов вы хотите их внедрить.
Кстати, шаблон Dependency Injection не привязан к Spring, это всего лишь инициализация поля класса. Это такая обычная вещь и так часто встречается в коде (через конструктор либо сеттер), что даже странно выделять ее в отдельный шаблон. В связи со Spring это название мелькает часто наверно потому, что внедрение выполняет Spring, и у программиста тут много возни с конфигурацией зависимостей. Но с другой стороны, создание и внедрение Spring-ом — это уже другой шаблон — инверсия контроля (IoC).
Класс ApplicationContext для работы с IoC Контейнером
Конфигурация Maven
Чтобы иметь возможность работать с контейнером, добавьте в pom.xml зависимость:
Последнюю версию зависимости можно взять тут.
Класс ApplicationContext
Для работы с контейнером существует не один класс. Но удобнее всего работать с классом ApplicationContext. Чтобы инициализировать контейнер и создать в нем бины, нужно создать экземпляр класса ApplicationContext.
Как уже сказано, контейнеру для создания бинов. требуется конгфигурация, так что конструктор контейнера принимает аргумент. Существуют два подкласса ApplicationContext: ClassPathXmlApplicationContext берет конфигурацию из XML-файла, а AnnotationConfigApplicationContext – из аннотаций:
Так что контейнер можно создать, используя именно тот вид конфигурации, которая используется в вашем приложении. Обычно это аннотации, XML немного устарел.
В конфигурации прописаны как исходные классы, которые надо сделать бинами, так и их зависимости, а также каким образом внедрить эти зависимости. Обычно зависимость внедряется либо через конструктор, либо через сеттер, в зависимости от дизайна класса.
После того, как мы инициализировали контейнер, и он создал бины, появляется возможность их получить непосредственно из контейнера. Хотя это не всегда нужно, но можно. Делается это так:
Мы получили бин типа Animal из контейнера в предположении, что он сконфигурирован в нашем приложении. Давайте теперь его на самом деле сконфигурируем.
Конфигурация бинов с помощью аннотаций
Задавать конфигурацию будем с помощью аннотаций, поскольку это более современный и удобный способ.
Во-первых, создадим класс конфигурации и аннотируем его с помощью @Configuration:
Во-вторых, допустим, у нас есть класс Animal, который мы хотим делать бином:
Чтобы сделать Animal бином, создадим в классе конфигурации метод,который создает и возвращает Animal, и аннотируем этот метод с помощью @Bean:
Все, бин сконфигурирован. Теперь экземпляр Animal будет создаваться контейнером автоматически, и мы сможем получить его из контейнера, как показано выше.
Возможно вы спросите, в чем смысл этого, ведь все равно мы создаем экземпляр Animal отдельным методом. Где же польза контейнера?
Да, мы прописываем метод, создающий Animal. Но мы не вызываем этот метод. Мы просто создаем контейнер. А метод вызывается контейнером во время его инициализации. И учитывая то, что бины обычно конфигурируются однотипно, выгода есть. К тому же они обычно создаются контейнером в единственном экземпляре (хотя это зависит от конфигурации) и внедряются в поля других бинов согласно конфигурации автоматически.
Причем вместо создания аннотированного @Bean метода, можно аннотировать класс Animal изнутри аннотацией @Component – а это и вовсе одна строчка.
О том, как сконфигурировать внедрение бинов в поля других бинов, читайте статью про внедрение зависимостей.
Разница между аннотациями @Bean и @Component в том, что @Bean более гибкая аннотация, ею мы аннотируем метод, а не класс:
Давайте сконфигурируем второй бин Man с помощью аннотации @Component:
Только учтите, что если мы аннотируем классы с помощью @Component, то в файл конфигурации надо добавить аннотацию @ComponentScan для автоматического поиска этих аннотированных классов. В ней надо указать имя пакета или нескольких пакетов, в который лежат эти классы. В этом пакете контейнер будет пытаться их найти автоматически.
Давайте добавим в файл конфигурации одну строчку с аннотацией @ComponentScan и именем пакета для поиска бинов:
Все бины, аннотированные с помощью @ComponentScan, должны лежать в пакете «ru.javalang.ioc».
Проверим, что оба бина действительно создаются:
Мы рассмотрели, что такое IoC-контейнер, как его создать и как из него получить бины. Также мы узнали, что можно сконфигурировать бины с помощью аннотаций @Bean и @Component.
Исходый код примера для этой статьи доступен на GitHub.
Inversion of Control: Методы реализации с примерами на PHP
О боже, ещё один пост о Inversion of Control
Каждый более-менее опытный программист встречал в своей практике словосочетание Инверсия управления (Inversion of Control). Но зачастую не все до конца понимают, что оно значит, не говоря уже о том, как правильно это реализовать. Надеюсь, пост будет полезен тем, кто начинает знакомится с инверсией управления и несколько запутался.
Другими словами, можно сказать, что все зависимости модулей должны строятся на абстракциях этих модулях, а не их конкретных реализациях.
Рассмотрим пример.
Пусть у нас есть 2 класса — OrderModel и MySQLOrderRepository. OrderModel вызывает MySQLOrderRepository для получения данных из MySQL хранилища. Очевидно, что модуль более высокого уровня (OrderModel) зависит от относительного низкоуровневого MySQLOrderRepository.
Пример плохого кода приведён ниже.
В общем и целом этот код будет отлично работать, выполнять возложенные на него обязанности. Можно было и остановиться на этом. Но вдруг у Вашего заказчика появляется гениальная идея хранить заказы не в MySQL, а в 1С. И тут Вы сталкиваетесь с проблемой — Вам приходится изменять код, который отлично работал, да и ещё и изменения вносить в каждый метод, использующий MySQLOrderRepository.
К тому же, Вы и не писали тесты для OrderModel…
И что же со всем этим делать?
1. Фабричный метод / Абстрактная фабрика
Одним из самых простых способов реализации инверсии управления является фабричный метод (может использоваться и абстрактная фабрика)
Суть его заключается в том, что вместо непосредственного инстанцирования объекта класса через new, мы предоставляем классу-клиенту некоторый интерфейс для создания объектов. Поскольку такой интерфейс при правильном дизайне всегда может быть переопределён, мы получаем определённую гибкость при использовании низкоуровневых модулей в модулях высокого уровня.
Рассмотрим выше приведённый пример с заказами.
Вместо того, чтобы напрямую инстанцировать объект класса MySQLOrderRepository, мы вызовем фабричный метод build для класса OrderRepositoryFactory, который и будет решать, какой именно экземпляр и какого класса должен быть создан.
2. Service Locator
Основная идея паттерна Service Locator заключается в том, чтобы иметь объект, который знает, как получить все сервисы, которые, возможно, потребуются. Главное отличие от фабрик в том, что Service Locator не создаёт объекты, а знает как получить тот или иной объект. Т.е. фактически уже содержит в себе инстанцированные объекты.
Объекты в Service Locator могут быть добавлены напрямую, через конфигурационный файл, да и вообще любым удобным программисту способом.
3. Dependency Injection
Setter injection
При таком методе внедрения в классе, куда внедрятся зависимость, создаётся соответствутющий set-метод, который и устанавливает данную зависимость
Constructor injection
Interface injection
Такой метод внедрения зависимостей очень похож на Setter Injection, затем исключением, что при таком методе внедрения класс, куда внедрятся зависимость, наследуется от интерфейса, который обязует класс реализовать данный set-метод.
Какие проблемы данная реализация не решает?
По правде говоря, я не вижу во внедрении зависимостей каких-то больших недостатков. Это хороший способ сделать класс гибким и максимально независимым от других классов. Возможно это ведёт к излишней абстракции, но это уже проблема конкретной реализации принципа программистом, а не самого принципа
4. IoC-контейнер
IoC-контейнер — это некий контейнер, который непосредственно занимается управлением зависимостями и их внедрениями (фактически реализует Dependency Injection)
IoC-контейнеры присутствует во многих современных PHP-фреймворках — Symfony 2, Yii 2, Laravel, даже в Joomla Framework 🙂
Главное его целью является автоматизация внедрения зарегистрированных зависимостей. Т.е. вам необходимо только лишь указать в конструкторе класса необходимый интерфейс, зарегистрировать конкретную реализацию данного интерфейса и вуаля — зависимость внедрена в Ваш класс
Работа таких контейнеров несколько отличается в различных фреймворках, поэтому предоставляю вам ссылки на официальные ресурсы фреймворков, где описано как работают их контейнеры
Заключение
Тема инверсии управления поднималась уже миллионы раз, сотни постов и тысячи комментариев на эту тему. Но тем не менее, всё также я встречаю людей, вижу код и понимаю, что данная тема ещё не очень популярна в PHP, несмотря на наличие отличных фреймворков, библиотек, позволяющих писать красивый, чистый, читаемый, гибкий код.
Надеюсь статья была кому-то полезная и чей-то код благодаря этому станет лучше.
Пишите свои замечания, пожелания, уточнения, вопросы — буду рад
Использование IoC контейнеров. За и Против
В свете выхода новой версии Enterprise Library, одной из важнейших частей которой является IoC-контейнер Unity, лично я ожидаю всплеск интереса к теме IoC(Inversion of Control) контейнеров, которые, по сути, являются реализацией достаточно известного паттерна Service Locator.
В этом топике я хотел бы подискутировать на тему «правильного» использования таких контейнеров, а также предостеречь новичков от применения его «везде и всюду». Ну и просто интересующимся возможностями «новых технологий» тоже должно быть любопытно.
Предыстория проблемы
Обычно к знакомству с IoC-контейнерами подталкивает осознание необходимости следования принципам проектирования классов (SOLID), а именно, последний принцип, который говорит о том, что каждый класс должен зависеть не от конкретных объектов, а от абстракций(интерфейсов), и совсем замечательно, когда все эти зависимости объявляются в конструкторе.
class TextManager <
public TextManager(ITextReader reader, ITextWriter writer) <
>
>
это хорошо, а код вида:
class TextManager <
public TextManager(TextFromFileReader reader) <
>
При этом, если приложение/библиотека у вас большая, ITextWriter используется во многих классах, а какой именно Writer будет использоваться определяется на входе в библиотеку (допустим, у нас есть Writer’ы в БД и файл с общим интерфейсом), то логично возникает желание как-то связать ITextWriter с DatabaseWriter’ом где-то в одном месте.
До SOLID-ов считалось вполне нормальным объявить внутри библиотеки в статическом классе переменную типа ITextWriter и хранить конкретный используемый на текущий момент Writer там. Но когда начинаешь задумываться о нормальной архитектуре… 🙂 минусы статичных классов становятся очевидными — совершенно непрозрачно от чего именно каждый класс зависит, что ему нужно для работы, а что нет, и страшно даже представить, что «потянется» вместе с этим классом, если вдруг необходимо будет его перенести в другой проект или хотя бы другую библиотеку.
IoC-контейнер
Что же предлагается нам для решения проблемы? Решение давно придумано в виде IoC-контейнеров: мы «связываем» интерфейсы с конкретными классами, как только получаем необходимую информацию:
var container = new UnityContainer();
container.RegisterInstance ( new DatabaseWriter());
и имеем удобный способ создания объектов:
И если на примере одной зависимости, преимущество контейнеров неочевидно, то если представить, что конструкторы требуют 2-3 интерфейса, и таких классов у нас хотя бы 5-10, то о применении контейнеров покажется многим настоящим спасением.
Когда контейнер — хорошо..
Собственно, Unity и создавался для активного применения в сложных составных приложениях, с множеством реализаций идентичных интерфейсов и нелинейной логикой взаимозависимостей между реализациями. Говоря проще, если у нас на всю библиотеку не один-единственный интерфейс IWriter с двумя реализациями DbWriter и TextWriter, а еще, к примеру, IReader и ITextProcessor, для каждого из которых тоже существует 3-4 реализации, и TextWriter работает только с CsvReader’ом и ExcelReader’ом, а какой именно из ридеров надо использовать, зависит от конкретного типа текущего TextProcessor’а, ну и от фазы луны заодно.
Очень сложно привести конкретные примеры кода, применение Unity в котором было бы, с моей точки зрения, обоснованно, и не загромоздить текст тонной ненужного кода. Но описанный «пример» создает некое подобие такой сложной и комплексной задачи 🙂
А когда — не очень
. Profit!
Подытоживая, мне кажется, что использование IoC-контейнеров идеально именно в ситуациях со сложными переплетениями взаимозависимостей между конкретными реализациями, и только на самых верхних уровнях библиотеки/приложения.
То есть сразу после точки входа в библиотеку следует регистрировать в Юнити реализации интерфейсов, и как можно раньше резолвить необходимые нам типы. Таким образом мы пользуемся всей мощью Юнити по упрощению процесса генерации сложнозависимых объектов, и в то же время следуем принципам хорошего дизайна и сводим к минимуму использование весьма абстрактного «контейнера» в нашем приложении, тем самым упрощая его тестирование.
Реализация своего IoC контейнера
Введение
Каждый начинающий разработчик должен быть знаком с понятием Inversion of Control (Инверсия управления).
Практически каждый новый проект сейчас начинается с выбора фреймворка, с помощью которого будет реализован принцип внедрения зависимостей.
Инверсия управления (Inversion of Control, IoC) — важный принцип объектно-ориентированного программирования, используемый для уменьшения связанности в компьютерных программах и входящий в пятерку важнейших принципов SOLID.
На сегодня существуют несколько основных фреймворков по этой теме:
По сей день пользуюсь Spring и частично доволен его функционалом, но пора бы попробовать что-то и свое, не правда ли?
О себе
Зовут меня Никита, мне 24 года, и я занимаюсь java (backend) на протяжении 3 лет. Обучался только на практических примерах, параллельно пытаясь разобраться в спеках классов. На данный момент работаю (freelance) — написание CMS для коммерческого проекта, где и использую Spring Boot. Недавно посетила мысль — «Почему бы не написать свой IoC (DI) Container по своему видению и желанию?». Грубо говоря — «Захотелось своего с блекджеком. ». Об этом и пойдет сегодня речь. Что ж, прошу под кат. Ссылка на исходники проекта.
Особенности
— ленивая инициализация компонентов (по требованию);
— встроенные файлы конфигурации загрузчика (форматы: ini, xml, properties);
— обработчик аргументов командной строки;
— обработка модулей путем создания фабрик;
— встроенные события и слушатели;
— встроенные информаторы (Sensibles) для «информирования» компонента, фабрики, слушателя, процессора (ComponentProcessor) о том, что определенная информация должна быть загружена в объект в зависимости от информера;
— модуль для управления / создания пула потоков, объявление функций как исполняемых задач в течение некоторого времени и инициализация их на фабрике пулов, а также начиная с параметров SimpleTask.
Используется Reflections API стороннего разработчика со стандартным сканером.
Получаем коллекцию классов с помощью фильтров аннотаций, типов.
В данном случаи это @IoCComponent, @Property и прородитель Analyzer
1) В первую очередь происходит инициализация конфигурационных типов.
* Пояснения:
Аннотация @Property имеет обязательный строковый параметр — path (путь к файлу конфигурации). Именно по нему ведется поиск файла для парсинга конфигурации.
Класс PropertiesLoader — класс-утилита для инициализирования полей класса соответствующих полям файла конфигурации.
Функция DependencyFactory#addInstalledConfiguration(Object) — загружает объект конфигурации в фабрику как SINGLETON (иначе смысл перезагружать конфиг не по требованию).
2) Инициализация анализаторов
3) Инициализация найденных компонентов (Классы помеченные аннотацией @IoCComponent)
3) Методы сканирования
— метод инъекции параметров в конструктор класса
— метод инъекции параметров в поля класса
— метод инъекции параметров через функции класса
1. ComponentProcessor — утилита позволяющая изменять компонент по собственному желанию как до его инициализации в контексте так и после.
*Пояснения:
Функция #afterComponentInitialization(String, Object) — позволяет проводить манипуляции с компонентом после инициализации его в контексте, входящие параметры — (закрепленной название компонента, инстанциированный объект компонента).
Функция #beforeComponentInitialization(String, Object) — позволяет проводить манипуляции с компонентом перед инициализацией его в контексте, входящие параметры — (закрепленной название компонента, инстанциированный объект компонента).
*Пояснения:
Функция #resolve(String. ) — интерфейс-обработчик различных команд переданных через cmd при запуске приложения, входящий параметр — неограниченный массив строк (параметров) командной строки.
3. Информаторы (Sensibles) — указывает, что дочернему классу информатора нужно будет встроить опр. функционал в зависимости от типа информатора (ContextSensible, EnvironmentSensible, ThreadFactorySensible, etc.)
4. Слушатели (Listener’s)
Реализован функционал слушателей, гарантировано multi-threading выполнение с настройкой рекомендуемого количества дескрипторов для оптимизированной работы событий.
** Пояснения:
Функция dispatch(Event) — главная функция обработчик событий системы.
— Присутствуют стандартные реализации слушателей с проверкой на типы событий а так же со встраиваемыми пользовательскими фильтрами <@link Filter>. Стандартные фильтры входящие в пакет: AndFilter, ExcludeFilter, NotFilter, OrFilter, InstanceFilter (custom). Стандартные реализации слушателей: FilteredListener и TypedListener. Первый задействует в себе фильтр для проверки входящего объекта события. Второй осуществляет проверку событийного объекта либо же любого другого на принадлежность к определенному инстансу.
1) Модуль для работы с потоковыми задачами в Вашем приложении
— маркер-аннотация для включения модуля в контекст (@ThreadingModule)
— внедрение фабрики модуля в инсталлируемый компонент приложения
*Пояснения:
ThreadFactorySensible — один из дочерних классов-информаторов для внедрения в инстанциируемый компонент опр. информации (конфигурации, контекста, модуля, etc.).
DefaultThreadingFactory — фабрика модуля threading-factory.
Аннотация @SimpleTask — параметризируемый маркер-аннотация для выявления у компонента реализации задач в функциях. (запускает поток с заданными параметрами аннотацией и добавляет его в фабрику, откуда его можно будет достать и, к примеру, отключить выполнение).
— стандартные функции шедулинга задач
***Обратите внимание, что ресурсы в пуле запланированных потоков ограничены, и задачи должны выполняться быстро.
— дефолтная конфигурация пула
Стартовая точка или как это все работает
Подключаем зависимости проекта:
Тестовый класс приложения.
**Пояснения:
Аннотация @ScanPackage — указывает контексту, какие пакеты следует сканировать для идентификации компонентов (классов) для их инъекции. Если пакет не указан, будет сканироваться пакет класса, помеченного этой аннотацией.
IoCStarter#start(Object, String. ) — точка входа и инициализации контекста приложения.
Дополнительно создадим несколько классов-компонентов для непосредственной проверки функционала.
* Примечания:
— циклические зависимости не предусмотрены, стоит заглушка в виде анализатора, который, в свою очередь, проверяет полученные классы из отсканированных пакетов и выбрасывает исключение, если имеется циклика.
**Пояснения:
Аннотация @IoCComponent — показывает контексту, что это компонент и его нужно проанализировать для выявления зависимостей (обязательная аннотация).
Аннотация @IoCDependency — показывает анализатору, что это зависимость компонента и ее нужно инстанциировать в компонент.
Аннотация @LoadOpt — показывает контексту, какой тип загрузки компонента нужно использовать. В данный момент времени поддерживается 2 типа — SINGLETON и PROTOTYPE (единичный и множественный).
Расширим реализацию main-класса:
Запускаем средствами Вашей IDE или командной строкой проект.
+ имеется встроенное апи парсинга конфигурационных файлов (ini, xml, properties).
Обкатанный тест лежит в репозитории.
Будущее
В планах расширять и поддерживать проект на сколько это будет возможно.
Всем спасибо. Надеюсь кому-то мои труды пригодятся.
UPD. Обновление статьи — 15.09.2018. Релиз 1.0.0
IoC, DI, IoC-контейнер — Просто о простом
Думаю сейчас слова IoC, DI, IoC-контейнер, как минимум у многих на слуху. Одни этим активно пользуются, другие пытаются понять, что же это за модные веяния.
На данный момент, на эту тему уже довольно сказано, написано, в том числе и на хабре, но как раз из-за обилия информации сложно найти действительно полезный контент. Кроме того, данные понятия часто смешивают и/или путают. Проанализировав множества материалов я решил изложить вам свое видение предмета.
Теория
Для меня взаимосвязь между IoC и DI такая же как между Agile и Scrum, т.е.
Inversion of Control (инверсия управления) — это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.
Dependency Injection (внедрение зависимостей) — это одна из реализаций этого принципа (помимо этого есть еще Factory Method, Service Locator).
IoC-контейнер — это какая-то библиотека, фреймворк, программа если хотите, которая позволит вам упростить и автоматизировать написание кода с использованием данного подхода на столько, на сколько это возможно. Их довольно много, пользуйтесь тем, чем вам будет удобно, я продемонстрирую все на примере Ninject.
Практика
Согласно подходу инверсии управления если у нас есть клиент, который использует некий сервис, то он должен делать это не напрямую, а через посредника, своего рода аутсорсинг.
То как технически это будет сделано и определяет каждая из реализаций подхода IoC.
Мы будем использовать DI, на простом примере:
Скажем есть некий класс, который создает расписание, а другой класс его отображает (их как правило много, скажем один для десктоп-приложения, другой для веба и т.д.).
Если бы мы ничего не знали о IoC, DI мы бы написали что-то вроде этого:
Вроде бы все хорошо, код решает поставленную задачу, но что если мы захотим в последствии изменить реализацию менеджера расписаний и/или же иметь несколько таких менеджеров и динамически их заменять. Тогда в последствии нам придется менять и что-то в ScheduleViewer, а значит и снова его тестировать.
К счастью, разработчики, ленивые люди в хорошем смысле этого слова, и не любят делать одно и тоже дважды.
Мы, например, воспользуемся внедрением зависимостей (DI) для того, чтобы разорвать этот клубок стальных ниток — сделаем связь между этими классами более слабой, добавив прослойку в виде интерфейса IScheduleManager. И будем разрешать ее одним из способов техники DI, а именно Constructor Injection (помимо этого есть Setter Injection и Method Injection — если в двух словах, то везде используется интерфейс вместо конкретного класса, например в типе свойства или в типе аргумента метода):
И далее там где мы хотим воспользоваться нашим классом для отображения расписания мы пишем:
Вот уже почти идеально, но что если у нас много различных ScheduleViewer, разбросанных по проекту, которые использует всегда именно ScheduleManager (придется его руками каждый раз создавать) и/или мы хотим как-либо настроить поведение, так что бы в одной ситуации везде использовать ScheduleManager, а в другой скажем AnotherScheduleManager и т.д.
Решить эту проблему как раз и призваны IoC-контейнеры.
IoC-контейнеры
Они помогают уменьшить количество рутины, позволяя задать соответствие между интерфейсом и его конкретной реализацией, чтобы потом везде этим пользоваться.
Как я уже говорил выше, мы будем рассматривать это на примере Ninject —
1. Сначала мы создаем конфигурацию контейнера:
Теперь везде где требуется IScheduleManager будет подставляться ScheduleManager.
2. Создаем сам контейнер, указывая его конфигуратор:
Контейнер сам создаст экземпляр класса ScheduleManager, вызовет конструктор ScheduleViewer и подставит в него свежесозданный экземпляр ScheduleManager.




