Существует целый ряд приемов, которые вы можете использовать, чтобы избежать необходимости зависеть от конкретного контейнера IoC в большей части вашего кода, простейшим из которых является использование инъекции в конструктор. Если вы не можете использовать шаблон локатора службы, просто создайте свой собственный класс локатора службы, который будет содержать ядро контейнера IoC, которое вы планируете использовать.
Тем не менее, целью контейнера IoC является достижение «инверсии управления»: то есть перемещение элемента управления с нижних уровней на верхние. Это означает, что вам нужно иметь точку рядом с «вершиной» (или «корнем») вашего приложения, которая фактически знает обо всех реализациях служб, которые оно будет использовать, а также о вашей конкретной реализации IoC. Это должно быть ограничено несколькими классами. Обычно «корень контекста» приложения - это место, где вы инициализируете свой контейнер IoC и локатор служб. Должен быть определенный модуль или группа модулей, которые позаботятся о настройке всех ваших привязок.
Если вы хотите разрешить использование плагинов, вам нужно создать специальный API для их использования и соответствия. Простое разрешение другим пакетам определять новые привязки IoC - это путь к катастрофе, поскольку вы не знаете, насколько хорошо эти разные пакеты будут играть вместе.
ASP.NET MVC 3 является хорошим примером этого. У них есть определенные локаторы фабрики сервисов, которые вы переопределяете в методе Global Application_Start. Чтобы реализовать одну из этих фабрик, вы должны соблюдать API, который они вам предоставляют. Но вы можете создать реализацию, которая использует любой контейнер IoC, который вы хотите, или ни одного вообще. Вы не меняете «привязки» вообще. Вы просто указываете платформе, что для текущего приложения вы хотите, чтобы оно использовало «эту фабрику» для создания контроллеров или поставщиков метаданных модели вместо использования фабрики по умолчанию.
Чтобы использовать другой пример, более подходящий для вашего конкретного примера, давайте рассмотрим случай ISearchProvider
. У вас может быть встроенный LuceneProvider
, и, возможно, один из ваших плагинов может предоставить GoogleProvider
. Каких из этих провайдеров вы хотите использовать? Означает ли простое присутствие GoogleProviderPlugin, что LuceneProvider больше не доступен? Должны ли поиски как-то объединять результаты обоих этих провайдеров? Может ли пользователь выбрать одного или нескольких поставщиков из пользовательского интерфейса?
Независимо от ответа на эти вопросы, в конечном итоге вы хотите, чтобы это контролировало ваше приложение, а не плагин. Вместо того, чтобы давать плагину карт-бланш для использования ваших привязок DI, вы хотите сказать плагину: «Я разрешаю вам определять дополнительных поставщиков поиска, и вот как вы можете их зарегистрировать». Они могут быть зарегистрированы различными способами, включая аннотации / атрибуты класса или просто наличие класса, который реализует данный интерфейс. Но важным моментом является то, что существует API, который конкретно определяет, к чему они могут «подключаться», и что вам нужно от любого, кто создает плагин.
Теперь, если GoogleProvider
имеет зависимости, которые определены в плагине, этот плагин может разрешать эти зависимости так, как он этого хочет. Надеюсь, он будет использовать какой-то IoC-контейнер, но если он этого не сделает, это не будет проблемой. Вы все еще можете быть агностиком в отношении типа контейнера, который они используют, если таковой имеется.
Если есть определенные службы, которые, как вы ожидаете, SearchProvider
потребуется, вы можете включить эти службы или фабрики для этих служб в состав API инициализации для вашего плагина. Таким образом, ваш плагин может получить доступ к этим сервисам, не зная о контейнере IoC вашего приложения.