Это на самом деле просто сделать, когда вы поймете, что DI - это шаблоны и принципы , а не технологии.
Чтобы разработать API-интерфейс, не зависящий от контейнера, следуйте этим общим принципам:
Программа для интерфейса, а не реализация
Этот принцип на самом деле является цитатой (из памяти) из Design Patterns , но он всегда должен быть вашей настоящей целью . DI это просто средство для достижения этой цели .
Применить принцип Голливуда
Голливудский принцип в терминах DI гласит: не вызывайте DI Container, он позвонит вам .
Никогда не запрашивайте зависимость напрямую, вызывая контейнер из своего кода. Спросите об этом неявно с помощью Constructor Injection .
Используйте конструктор инъекций
Когда вам нужна зависимость, запросите ее статически через конструктор:
public class Service : IService
{
private readonly ISomeDependency dep;
public Service(ISomeDependency dep)
{
if (dep == null)
{
throw new ArgumentNullException("dep");
}
this.dep = dep;
}
public ISomeDependency Dependency
{
get { return this.dep; }
}
}
Обратите внимание, как класс Service гарантирует свои инварианты. Как только экземпляр создан, зависимость гарантированно станет доступной из-за сочетания пункта Guard и readonly
ключевого слова.
Используйте Abstract Factory, если вам нужен недолговечный объект
Зависимости, внедренные с помощью Constructor Injection, обычно бывают долгоживущими, но иногда вам нужен недолговечный объект или для построения зависимости на основе значения, известного только во время выполнения.
Смотрите это для получения дополнительной информации.
Сочинять только в последний ответственный момент
Держите предметы развязанными до самого конца. Как правило, вы можете подождать и подключить все в точке входа приложения. Это называется корнем композиции .
Подробнее здесь:
Упростите, используя Фасад
Если вы чувствуете, что полученный API становится слишком сложным для начинающих пользователей, вы всегда можете предоставить несколько классов Facade, которые инкапсулируют общие комбинации зависимостей.
Чтобы обеспечить гибкий Фасад с высокой степенью обнаружения, вы можете рассмотреть возможность использования Fluent Builders. Что-то вроде этого:
public class MyFacade
{
private IMyDependency dep;
public MyFacade()
{
this.dep = new DefaultDependency();
}
public MyFacade WithDependency(IMyDependency dependency)
{
this.dep = dependency;
return this;
}
public Foo CreateFoo()
{
return new Foo(this.dep);
}
}
Это позволило бы пользователю создать Foo по умолчанию, написав
var foo = new MyFacade().CreateFoo();
Однако было бы очень легко обнаружить, что можно предоставить пользовательскую зависимость, и вы могли бы написать
var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();
Если вы представите, что класс MyFacade инкапсулирует множество различных зависимостей, я надеюсь, что ясно, как он будет обеспечивать правильные значения по умолчанию, в то же время делая расширяемость обнаруживаемой.
FWIW, долгое время после написания этого ответа, я расширил концепции и написал более длинный пост в блоге о библиотеках , дружественных к DI , и сопутствующий пост о DI-Friendly Frameworks .