Как избежать связывания с контейнером IoC

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

Как я могу позволить пользователям делать это, не требуя, чтобы они знали, какой контейнер IoC я использую?

Мое текущее промежуточное решение - структурировать мои сборки следующим образом:

1) Определите абстрактные сборки, содержащие только интерфейсы.

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

3) Определите привязки контейнеров в отдельных сборках; т.е. одна связка на каждую бетонную сборку.

Это означает, что конкретные сборки не связаны с конкретным контейнером IoC, и они были бы закрыты от изменений, если бы я использовал другой контейнер. Однако пользователям по-прежнему необходимо знать, какой контейнер использует моя структура, чтобы писать сборки привязки, и им нужно будет выпустить новые сборки привязки, если я изменил контейнер IoC (то есть с Ninject на Spring).

Я что-то упускаю?


person Lawrence Wagerfield    schedule 26.04.2011    source источник
comment
Проблема с этим подходом заключается в том, что вы ограничены наименьшим общим знаменателем того, какие контейнеры могут поддерживать, что во многих случаях не отвечает вашим внутренним потребностям. Я пробовал именно этот подход, и он вызывает слишком много затруднений у новичков в проекте.   -  person Chris Patterson    schedule 26.04.2011
comment
Что вы используете вместо этого?   -  person Lawrence Wagerfield    schedule 26.04.2011
comment
Платформа предоставляет явный набор точек регистрации и управляет внутренними зависимостями без контейнера. Точки регистрации включают фабричные методы (обычно основанные на Func ‹T›). Затем расширения контейнера (в отдельных сборках) могут регистрироваться и предоставлять фабричный метод для контейнера. Промежуточная модель для регистрации также может использоваться для передачи метаданных из контейнера во фреймворк.   -  person Chris Patterson    schedule 26.04.2011


Ответы (5)


Common Service Locator - это один из подходов, но он содержит методы только для разрешения, а не для регистрации.

Вы можете посмотреть, как это реализовано в проекте agatha-rrsl. Более полное объяснение есть здесь, но короче:

  • определить независимый от контейнера интерфейс для регистрации и разрешения типов
  • предоставлять реализации для различных контейнеров (или позволять пользователям отправлять реализации)

Предупреждение: вы, вероятно, не сможете напрямую использовать выбранный вами контейнер в своей библиотеке.

person jeroenh    schedule 26.04.2011
comment
Я раньше обдумывал эту идею; зная, что это делают другие, это кажется более приемлемым :) - person Lawrence Wagerfield; 26.04.2011
comment
Оу ... Это не лучшее решение. Я бы автоматически отказался от использования любых Fx, которые используют анти-шаблон Service Locator: blog .ploeh.dk / 2010/02/03 / ServiceLocatorIsAnAntiPattern.aspx - person Mark Seemann; 26.04.2011
comment
@Mark Seemann, почему вы так думаете? Service Locator как антишаблон - это ортогональная проблема для рассматриваемой проблемы. Более того, Agatha не поддерживает / не полагается / не продвигает Service Locator ... - person jeroenh; 26.04.2011
comment
Вы написали контейнерно-независимый интерфейс для регистрации и разрешения типов. Для меня это звучит так, будто вы извлекаете экземпляры из интерфейса в зависимости от их типа. Это определение местоположения службы: blog.ploeh.dk/2010/11/01 / - person Mark Seemann; 26.04.2011
comment
Этот вопрос / ответ вовсе не о местонахождении службы, да или нет. Мы обсуждаем способы предоставления возможностей IoC в повторно используемых библиотеках без привязки к конкретной реализации контейнера IoC. Я действительно не вижу ссылку на Service Location (что, я согласен, является антипаттерном, но совершенно не относится к теме обсуждения здесь). - person jeroenh; 26.04.2011
comment
Верно, но это облегчает жизнь. И я начинаю понимать, о чем на самом деле идет наш спор. По сути, вы говорите: как разработчик библиотеки вам не следует использовать контейнер. Хотя я говорю: как разработчик библиотеки, ЕСЛИ вы хотите использовать контейнер [который задал OP], вот как вы можете избежать привязки себя к конкретной реализации. Я по-прежнему не вижу, где место службы входит в картину. - person jeroenh; 26.04.2011

Напишите слабосвязанный код. Приложения должны зависеть от контейнеров. Фреймворки не должны.

person Mark Seemann    schedule 26.04.2011
comment
Хотя я ценю вашу точку зрения, я считаю неразумным просить пользователей связать все для себя в корне композиции. Конечно, они должны определять, когда происходит композиция, но разве не выгодно определять предустановленные привязки внутри модулей, которые пользователь может затем вызывать, используя свой собственный контейнер DI? Что, если V2 моего фреймворка требует дополнительной подсистемы-службы? Справедливо ли просить пользователя обновить свой корень композиции? - person Lawrence Wagerfield; 26.04.2011
comment
Вы можете указать разумные значения по умолчанию. Нет причин навязывать что-либо пользователям, но сделайте расширяемость возможной. - person Mark Seemann; 26.04.2011
comment
Это возвращает меня к исходной проблеме: как мне определить разумные значения по умолчанию, оставаясь при этом независимым от контейнера? Привязки определяются для контейнера ... - person Lawrence Wagerfield; 26.04.2011
comment
Предоставьте реализации по умолчанию для интерфейсов, которые использует ваш Fx. В любом случае, это хорошая практика дизайна Fx. Предоставьте пользователям швы, чтобы переопределить поведение по умолчанию. - person Mark Seemann; 26.04.2011
comment
Хорошо, просто напомним (поскольку я думаю, что наши провода перекрещиваются). Я определю IService с реализацией по умолчанию ServiceImpl. Где мне определить привязку по умолчанию из IService- ›ServiceImpl? И как мне определить эту привязку, не проходя через контейнер и, следовательно, оставаясь независимым от контейнера. - person Lawrence Wagerfield; 26.04.2011
comment
Взгляните на Facade Fluent Builder по указанной выше ссылке. - person Mark Seemann; 26.04.2011

Обычный подход заключается в абстрактном контейнере с помощью общего локатора служб

Автор MvcExtensions удалил IoC вполне успешно.

person Arnis Lapsa    schedule 26.04.2011
comment
Интерфейс общего локатора сервисов доступен только для чтения; вы не можете регистрировать услуги с его помощью. - person Lawrence Wagerfield; 26.04.2011

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

person Moonshield    schedule 26.04.2011

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

public class ComponentAttribute : Attribute
{}

// class that should be registered in the container.
[Component]
public class MyService : IMyService
{}

// Contains information for each class that should be 
// registered in the container.
public interface IContainerMapping
{
    public Type ImplementationType {get;}
    public IEnumerable<T> ImplementedServices {get; }
}

public class ComponentProvider
{
    public static IEnumerable<IContainerMapping> Find() 
    {
        var componentType = typeof(ComponentAttribute);
        foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
        {
           if (type.GetCustomAttributes(componentType, false).Count == 0)
              continue;

           var mapping = new ContainerMapping(type);
           List<Type> interfaces new List<Type>();
           foreach (var interfac in type.GetInterfaces())
           {
             //only get our own interfaces.
             if (interface.Assembly != Assembly.GetExecutingAssembly())
               continue;

             interfaces.Add(interfac);
           }

           mapping.ImplementedServices = interfaces;
           yield return mapping;
        }
    }
}

Это решение дает пользователю большую гибкость. Он может предоставить собственное решение, используя атрибут [Component] напрямую или используя ваше решение.

Пользователь должен сделать что-то вроде:

foreach (var mapping in ComponentProvider.Find())
    myContainer.Register(mapping.ImplementationType).As(mapping.ImplementedServices);

Обычно я создаю готовое решение, предоставляя проект MyProject.autofac, который регистрирует все в моем любимом контейнере.

person jgauffin    schedule 26.04.2011