Написание библиотеки с включенным внедрением зависимостей

В данный момент я работаю над проектом, и он будет в основном основан на библиотеке.

Я хочу, чтобы библиотека использовалась с использованием внедрения зависимостей, но я хочу, чтобы библиотека была в значительной степени независимой от используемого контейнера.

Некоторое время назад я написал «мостовую» библиотеку, чтобы упростить подобные вещи, но я не был уверен, что это действительно правильный подход? (библиотека: https://github.com/clintkpearson/IoCBridge)

Я не хочу ссылаться на DI-технологию (Ninject, Windsor и т. Д.) Непосредственно из моей библиотеки, поскольку это делает ее негибкой для людей, использующих ее.

Есть еще несколько вопросов по SO в аналогичном ключе, но ни один из них, похоже, на самом деле не решает проблему удовлетворительно.

В качестве побочного примечания: я понимаю, что могу просто убедиться, что библиотека придерживается общей идиомы и использует аргументы интерфейсов и ctor для зависимостей, а затем просто оставить это на усмотрение приложения-потребителя для регистрации типов в контейнеры.

Единственная проблема, которую я вижу в этом (и поправьте меня, если я ошибаюсь), заключается в том, что для этого требуется, чтобы приложение-потребитель действительно знал, какие типы связаны с какими интерфейсами, нужно ли регистрировать некоторые как одиночные. и т.д ... и с точки зрения использования plug-and-play это довольно плохо.


person Clint    schedule 15.02.2014    source источник
comment
Вам нужно разрешить пользователю вводить свои собственные реализации (плагины)?   -  person Ufuk Hacıoğulları    schedule 16.02.2014
comment
@ UfukHacıoğulları не обязательно нет. Скорее всего, они захотят реализовать свои собственные версии некоторых интерфейсов, а затем могут просто выбросить их вместо любых других ссылок. Однако сама библиотека, вероятно, не будет напрямую ориентирована на такую ​​реализацию.   -  person Clint    schedule 16.02.2014


Ответы (3)


Хорошая реализация DI должна включать DI для любого объекта, независимо от того, является ли последний независимым от DI или нет.

Prism - плохой пример, поскольку последний раз, который я использовал (2 года назад), он требовал, чтобы объекты были DI-агностическими, принудительно применяя атрибута [Injection]. Хорошим примером, не зависящим от DI, является Spring Framework (чрезвычайно популярная платформа DI для Java, имеет порт .NET под названием Spring.NET), который позволяет включать DI через так называемый < strong> файлы контекста - это файлы xml, описывающие зависимости. Последний не обязательно должен быть частью вашей библиотеки, оставив его как полностью независимый файл dll.

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

Это не означает, что любая структура DI должна поддерживать манипуляции с простыми объектами CLR (.NET), также известными как POCO-ы. Некоторые фреймворки полагаются только на свои конкретные механизмы и могут не подходить для использования с DI-независимым кодом. Обычно они требуют прямой зависимости от структуры DI для библиотеки, чего, я думаю, вы хотите (и, вероятно, должны) избегать.

person Ivaylo Slavov    schedule 17.02.2014
comment
Я действительно решил в конце концов последовать такому подходу. Я понял, что иметь загрузчик в библиотеке для регистрации вождения - плохой подход. Вместо этого я просто иду по маршруту фабрика / сервисный интерфейс с реализациями по умолчанию. Затем вызывающий код может использовать любую структуру, в которой они хотят внедрить зависимости, или сделать это вручную. Скорее всего, я соберу несколько специфичных для фреймворков оболочек, чтобы упростить эту задачу, и они будут выполнять автоматическое подключение от имени вызывающего кода. - person Clint; 18.02.2014
comment
Рад помочь :) - person Ivaylo Slavov; 19.02.2014

Это немного спорно, но я предлагаю использовать «Укол бедняка». Я не говорю, что это здорово, но у него есть несколько допустимых вариантов использования (например, Service Locator) с некоторыми ограничениями. Это потребует немного большего обслуживания, но это избавит вас от зависимости от другой библиотеки для регистрации контейнера IoC. Вам следует прочитать статью Марка Симанна по этой теме.

Я недавно реализовал этот подход в моей очень простой библиотеке. По сути, вы пишете два конструктора для общедоступных классов библиотеки.

internal SitemapProvider(IActionResultFactory actionResultFactory, IBaseUrlProvider baseUrlProvider)
{
    _actionResultFactory = actionResultFactory;
    _baseUrlProvider = baseUrlProvider;
}

public SitemapProvider() : this(new ActionResultFactory(), new BaseUrlProvider()) { }

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

Я не знаю, есть ли в .NET широко распространенная библиотека регистрации контейнеров IoC. Я сам думал написать такой, но у каждого контейнера есть свои уникальные особенности, и с жизненными циклами объектов все усложняется. Также людям будет неловко полагаться на другую библиотеку для этого.

person Ufuk Hacıoğulları    schedule 16.02.2014
comment
Спасибо! Есть несколько действительно интересных моментов. Некоторые из моих других проектов использовали вариации этих типов IoC. Я думаю, что лучший подход, вероятно, будет заключаться в том, чтобы сделать библиотеку слепой к тому факту, что она вообще будет внутри контейнера. За исключением простого, например, ITypeResolver интерфейс или что-то в этом роде. Тогда, возможно, есть библиотеки для каждого контейнера, которые предоставляют преобразователь для библиотеки и делают там всю магию контейнера. Я думаю, что для универсального использования и гибкости лучше всего подойдет мой IoCBridge. - person Clint; 16.02.2014

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

Для работы внедрения зависимостей вам не нужны фреймворки IoC.

Если вы действительно настаиваете на использовании контейнера IoC вместо использования обычного DI (например, параметров конструктора или обязательной настройки свойств), вам следует назначить контейнер / фреймворк, не пытайтесь быть всем для всех, пытаясь собрать вместе адаптеры или мосты. Будьте осторожны с чрезмерной инженерией. Библиотека по самому своему определению означает, что она имеет ограниченный и четко определенный набор функций, поэтому ей не нужно вводить большое количество зависимостей.

Скорее всего, они захотят реализовать свои собственные версии некоторых интерфейсов.

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

person slugster    schedule 15.02.2014
comment
-1 Поскольку это явно не для всех библиотек, некоторые из них довольно большие, несмотря на узкую область применения. Я также упомянул, что знал, что могу просто следовать шаблону в целом, и пользователь мог бы затем самостоятельно подключить все это, но это не совсем хорошо. Использование одного типа контейнера IoC для библиотеки также не очень хорошо, потому что тогда некоторые пользователи не смогут особенно хорошо интегрировать его в свои собственные приложения. Такие библиотеки, как Prism / Caliburn.Micro, поддерживают разные контейнеры IoC, так что это явно готовая вещь. - person Clint; 16.02.2014
comment
@ Клинт Мне очень жаль, что я не понял твою точку зрения. Размер и объем библиотеки не обязательно пропорциональны, и если вы сделаете одно небольшое предложение и придумаете, это означает, что вы пропустили то, что я сказал - возможно, потому, что то, что я сказал, не соответствует вашему предубеждению. Если я чего-то не понял, непременно попросите меня объяснить. Если вы полны решимости переусердствовать, я не могу вас остановить, но могу посоветовать вам этого не делать. - person slugster; 16.02.2014
comment
@Clint Твой комментарий о Prism заставляет меня думать, что ты совершенно неправильно понял то, что я говорил. Прочтите о Prism здесь. Я думаю, в этой статье совершенно ясно, что вызывающий код выполняет разрешение, поэтому вашей библиотеке не нужно знать ни о каком контейнере. Фактически, передача контейнера IoC для вызывающего абонента может быть анти-шаблоном. - person slugster; 16.02.2014
comment
У Prism есть тип загрузчика, на который он опирается для запроса экземпляров из внешнего источника, поэтому он ориентирован на поддержку контейнеров. В этом суть моего вопроса. Как лучше всего добиться этого в библиотеке? - person Clint; 16.02.2014
comment
@IvayloSlavov Отличный набор комментариев, закройте их как ответ. - person slugster; 18.02.2014
comment
@Clint, проверьте мой ответ. Я надеюсь, что пересмотрю вопрос о понижении этого ответа, поскольку он не говорит ничего плохого. Если вы настаиваете на использовании Prism, вам лучше проверить, можете ли вы заставить его работать для обычных объектов, или переформулируйте свой вопрос, включив в него то, что он вам нужен для конкретной работы с Prism. Фреймворки DI различаются, и некоторые из них не столь либеральны и совместимы с кодом, отличным от DI, как Spring. - person Ivaylo Slavov; 18.02.2014
comment
@slugster, спасибо. Я удалил комментарии и добавил ответ. Интересно, почему я не сделал этого в первую очередь, но, к счастью, ТАК похож на вики - person Ivaylo Slavov; 18.02.2014