IoC - поддержка нескольких реализаций для одного интерфейса
Мне интересно, почему .NET IoC-контейнеры не поддерживают несколько реализаций для одного интерфейса! Возможно, я ошибаюсь, но, насколько я понял, фреймворки вроде Ninject частично поддерживают эту функцию, используя аннотации (как?). Я не думаю, что другие платформы, такие как Windsor или простой инжектор, имеют простой механизм для поддержки этого сценария.
Есть ли причина, почему это не поддерживается многими фреймворками? AFAIK, одна из самых важных причин использования интерфейсов, заключается в том, чтобы добиться ослабления сцепления. Если рамки, разработанные для улучшения ослабленной связи, не плавно поддерживают несколько реализаций для одного интерфейса, я не понимаю, почему!
P.S. Конечно, я понимаю, что во время выполнения будет проблема с разрешением, и контейнер будет путать, какую реализацию выбрать, но это то, что нужно учитывать в дизайне, не так ли?
Ответы
Ответ 1
Unity имеет ту же функциональность
Зарегистрировать именованную зависимость
var container = new UnityContainer();
container.RegisterType<IConnector, Connector>("TestConnector");
Решить по имени
container.Resolve<IConnector>("TestConnector");
тот же подход
[Dependency("TestConnector")]
public IConnector Connector { get; set; }
Windsor имеет тот же
class Program
{
static void Main(string[] args)
{
var container = new WindsorContainer()
.Register(Component.For<IConnector>().ImplementedBy<ConnectorA>().Named("ConnectorA"))
.Register(Component.For<IConnector>().ImplementedBy<ConnectorB>().Named("ConnectorB"));
var connectorA = container.Resolve<IConnector>("ConnectorA");
Console.WriteLine("Connector type: {0}", connectorA.GetType());
var connectorB = container.Resolve<IConnector>("ConnectorB");
Console.WriteLine("Connector type: {0}", connectorB.GetType());
Console.ReadKey();
}
}
public interface IConnector
{
}
public class ConnectorA : IConnector
{
}
public class ConnectorB : IConnector
{
}
Ответ 2
Я советую взглянуть на Конвенцию по вопросам конфигурации и, в частности, на инъекцию зависимостей на основе Конвенции и навязку на основе контекста. Большинство IoC, если не все, поддерживают оба подхода. Вы можете найти много интересных образцов с различными библиотеками IoC, когда несколько реализаций привязаны к одному интерфейсу и насколько это полезно.
Например ninject поддерживает привязку нескольких реализаций одного интерфейса: зависит от контекста или атрибутов, по именам и т.д..
В контексте
Следующие привязки фрагмента к выполнению зависят от типа цели автоматически:
Bind<IWarrior>().To<Samurai>().WhenInjectedInto(typeof(OnLandAttack));
Bind<IWarrior>().To<SpecialNinja>().WhenInjectedInto(typeof(AmphibiousAttack));
По имени
Очень полезно, когда вы настроены в формате XML или базы данных. Также учтите InNamedScope
:
Bind<IWeapon>().To<Shuriken>().Named("Strong");
Bind<IWeapon>().To<Dagger>().Named("Weak");
С другой конфигурацией зависимостей в разных частях вашего проекта.
Ответ 3
Ваша предпосылка неверна.
Windsor довольно счастливо принимает регистрацию нескольких реализаций одной и той же службы. В дополнение к названной поддержке разрешения компонентов, упомянутой GSerjo, в Windsor (по умолчанию) первая зарегистрированная реализация победит, но вы можете переопределить ее, используя метод IsDefault()
при регистрации альтернативной реализации. Подробнее см. http://docs.castleproject.org/Windsor.Registering-components-one-by-one.ashx.
Если вы хотите больше контролировать выбор из нескольких реализаций, вы можете создать реализацию IHandlerSelector для этого. Подробнее см. http://stw.castleproject.org/Windsor.Handler-Selectors.ashx.
Ответ 4
Мой контейнер Griffin.Container поддерживает его.
registrar.RegisterConcrete<OneImplementation>();
registrar.RegisterConcrete<AnotherImplementation>();
И чтобы получить:
var services = container.Resolve<ITheService>();
Однако вы не можете получить одну конкретную реализацию. Это дизайнерское решение. Гораздо лучше зарегистрировать factory в контейнере, если нужно получить конкретную реализацию. Подробнее здесь в разделе лучших практик.
Griffin.Container можно найти в github: https://github.com/jgauffin/griffin.container
Ответ 5
StructureMap предоставляет следующие возможности:
For<IMyInterface>().Add<MyInterfaceImpl1>().Named("MyInterfaceImpl1");
For<IUsingInterface>().Add<UsingInterfaceImpl>().Ctor<IMyInterface>().Is(i => i.GetInstance<IMyInterface>("MyInterfaceImpl1"));
Ответ 6
Ваш вопрос немного расплывчатый, поскольку вы не приводите конкретный пример того, когда вы думаете, что вам это нужно. В большинстве случаев существует проблема в вашем приложении или дизайне, или вы не следуете рекомендациям DI.
Все контейнеры позволяют регистрировать несколько зависимостей с тем же интерфейсом, что и IEnumerable<ThatInterface>
, даже если они не имеют глубокой поддержки для нескольких экземпляров. Однако включение списков сервисов в другие сервисы - это дизайнерский запах, и лучше было бы скрыть этот список за Composite. Это скрывает тот факт, что за абстракцией реализовано несколько реализаций и позволяет легко изменить способ использования этих множественных реализаций, изменив только одно место в приложении. Я не верю, что ни одна инфраструктура IoC не имеет поддержки для создания композитов для вас, так как нет единственного способа обработки завершенных реализаций по умолчанию. Вам придется написать этот композит самостоятельно. Однако, поскольку запись такого композита действительно, действительно простая, это оправдывает отсутствие такой функции в рамках.
Если вы хотите иметь несколько реализаций, но всегда нуждаетесь в том, чтобы их возвращали, на основе некоторой конфигурации, всегда есть способы сделать это. Большинство контейнеров позволяют настраивать эти зависимости в файле конфигурации XML. Но даже если контейнер не содержит такой функции, простое считывание этого значения из файла конфигурации вручную и регистрация правильного типа в контейнере очень просто.
Если у вас есть одна реализация определенного интерфейса для производства и другая реализация для модульного тестирования, вы должны только зарегистрировать производственную реализацию в контейнере. Тестирование вашего устройства должно быть очищено от любого контейнера DI, и вы должны вручную создать тестируемый класс и ввести поддельные зависимости в свой конструктор, просто new
введите тип вверх. Использование контейнера DI, загрязняет и усложняет ваши тесты. Чтобы снять это, вам нужно спроектировать такой тип вокруг шаблона инъекции конструктора. Не вызывайте контейнер (или любой другой фасад над контейнером) внутри тестируемой службы для получения зависимостей.