Это на самом деле просто сделать, когда вы поймете, что DI - это паттерны и принципы , а не технология.
Чтобы разработать API-интерфейс, не зависящий от контейнера, следуйте этим общим принципам:
Программа для интерфейса, а не реализация
Этот принцип на самом деле является цитатой (из памяти) из Design Patterns , но это всегда должна быть ваша реальная цель . DI - всего лишь средство для достижения этой цели .
Применить принцип Голливуда
Голливудский принцип в терминах DI гласит: Не вызывайте DI-контейнер, он позвонит вам .
Никогда не запрашивайте зависимость напрямую, вызывая контейнер из вашего кода. Запрашивайте это неявно, используя Конструктор Injection .
Использование конструктора 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-Friendly Libraries и сопутствующий пост о DI-Friendly Frameworks .