как писать библиотеки, не заставляя пользователей использовать контейнер IOC библиотеки - PullRequest
8 голосов
/ 24 марта 2012

Короткий вопрос: Учитывая, что библиотека гарантирует , используя конкретный контейнер IOC для своих внутренних объектов, когда приложение использует эту библиотеку, учитывая, что приложение гарантирует , используя контейнер IOC для подключения своих зависимостей, учитывая, что два Контейнеры разные, как они могут хорошо играть вместе?

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

Вот длинный вопрос:

Этот вопрос, кажется, задавался в разных формах и формах ранее на SO, но я не могу найти ответ, который мне нужен, поэтому я собираюсь взглянуть на него с гипотетическим _over_simplified_ конкретным примером. Мы хотим написать библиотеку для регистрации, которую пользователи могут включить в свое решение как пакет, чтобы получить функциональность регистрации из коробки. Общедоступные интерфейсы, которые предоставляет библиотека ..

public interface ILogger {}

public interface ITarget {}

Конкретные реализации

internal class Logger: ILogger { public Logger(ITarget target) {}}

internal class FileTarget : ITarget {}

Требования заключаются в том, что если пользователь включает наш пакет и определяет класс со свойством типа ILogger или имеет аргумент ctor типа ILogger, то наша библиотека отвечает за внедрение конкретной реализации для этого интерфейса в определенный пользователем учебный класс. По умолчанию этот внедренный регистратор отправляется в файловую систему для ведения журнала, потому что реализация по умолчанию ITarget, внедренная в реализацию ILogger, - это FileTarget нашей библиотекой.

Если пользователь решит написать класс, реализующий интерфейс ITarget, тогда наша библиотека будет использовать его для внедрения в класс Logger и не будет использовать реализацию по умолчанию FileTarget.

ТАК, что я хочу продемонстрировать, это их двунаправленная зависимость.

  1. Наша библиотека зависит от сборок пользователя, поскольку ей необходимо сканировать сборки пользователя для загрузки любых точек расширения (например, реализации ITarget) и внедрять их в свои собственные объекты перед любыми реализациями по умолчанию.

  2. Сборки пользователя зависят от библиотеки, так как если пользователь решит определить класс с интерфейсом ILogger в качестве зависимости, то этот пользовательский объект должен получить конкретную ссылку на этот интерфейс, предоставленный нашим пользователем во время выполнения. библиотека.

Простое решение: если пользователь и наша библиотека используют один и тот же контейнер IOC, тогда проблема решена. Но это сильное предположение. Что я хочу сделать, это

  1. Используйте контейнер IOC с библиотекой, которая лучше всего соответствует требованиям библиотеки, в моем случае это Ninject.
  2. Во время выполнения каким-то образом предоставьте пользователю механизм вызова через какой-либо API в мою библиотеку, который обеспечит запуск Ninject, сканирование пользовательских сборок и передачу всех данных с учетом всех точек расширения.

Пока все хорошо, это вполне достижимо, но тут начинается сложная часть.

  • если пользователь также использует Ninject, то проблема автоматически решается, поскольку Ninject уже знает, как разрешать интерфейсы, живущие в нашей библиотеке. Но что, если пользователь решит использовать свой выбор контейнера IOC?

Я почти хочу определить какую-то функциональность дочернего контейнера в библиотеке с интерфейсом, подобным

public interface IDependencyResolvingModule { T Get<T>(); []T GetAll<T>(); }

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

Я хочу, чтобы контейнер IOC пользователя имел некоторые функциональные возможности, и если он не может разрешить зависимость (например, ILogger), он должен подключиться к реализации IDependencyResolvingModule и запросить зависимость.

Таким образом, наша библиотека может использовать свой выбор контейнера IOC, а код пользователя может разрешать зависимости, о которых ее контейнер IOC не имеет никакого понятия. Разве это решение не сработало бы, если бы IOC-контейнеры там каким-то образом обеспечивали функциональность для регистрации одноэлементных экземпляров любых IDependencyResolverModules, найденных в сборках в каталоге исполняемой сборки, и когда они не могут разрешить тип, спросите какой-либо из одноэлементных модулей?

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

1 Ответ

3 голосов
/ 27 марта 2012

Я вижу несколько возможных подходов:

  1. Написать регистратор по умолчанию для всех популярных контейнеров IoC.Каждый из них должен быть размещен в отдельной сборке.Затем разработчик может выбрать тот, который ему нужен, и настроить свой контейнер с ним.

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

  3. Ленивый вариант первого подхода.Напишите небольшое руководство по настройке контейнера для работы с реализациями по умолчанию.Тогда разработчик может сам настроить контейнер.

  4. Объедините все предыдущие решения, чтобы удовлетворить каждого разработчика.:)

РЕДАКТИРОВАТЬ: добавлен пример интеграции двух контейнеров

var allPublicInterfacesFromLibrary = typeof(AnyLibraryType)
    .Assembly.GetTypes()
    .Where(t => t.IsInterface && t.IsPublic);

foreach (var libraryInterface in allPublicInterfacesFromLibrary)
{
    var local = libraryInterface; //to prevent closure
    applicationContainer.Register(
        Component.For(libraryInterface)
        //delegate resolving
        .UsingFactoryMethod(k => libraryContainer.Resolve(local)) 
        //delegate lifetime management
        .LifestyleTransient() 
    );
}
...