Microsoft 文档中的建议并没有错,但它相当简单,因为它缺乏很多上下文。我真的不怪作者,因为可以写出关于这个主题的整本书(exactly what I did)。
有很多事情要考虑。首先要理解的一个重要概念是Composition Root。它是应用程序中注册依赖项和构建对象图的部分。
另一个重要的概念是Service Locator anti-pattern。正如 anti-pattern 标签所暗示的,这是一种应该始终避免的模式。当您允许代码(位于组合根之外)解析任何服务时,您正在应用此反模式。例如,当您将Func<Type, object> 委托注入消费者或将IServiceProvider 注入消费者时,就会出现这种情况。它的GetService 方法具有该委托。
另一方面,对于在组合根中的代码,没有服务定位器的概念;这种反模式在那里不存在。这是因为 Composition Root 的主要功能是构造类型。它已经直接依赖于 DI 容器。组合根预计会解析服务。
然而,重要的是要注意,具有特定委托不与服务定位器相同。例如,如果你注入一个Func<IMyService>,这不是一个服务定位器,因此,这不是一个坏主意。
但还有更多需要考虑。通常,您应该以这样一种方式定义抽象,即其使用者不必考虑实例的数量。从抽象消费者的角度来看,为了降低复杂性,这些消费者应该能够像只有一个实例一样调用他们的抽象。
这并非在所有情况下都是可能的,但可以认为是一个很好的经验法则。但是当你应用这个经验法则时,工厂抽象的空间很小(例如Func<IMyService>),因为它们立即打破了这个经验法则;消费者主动创建多个实例。
但是即使消费者可能认为他们的依赖是无状态的,依赖的实现也可能不是。这仍然意味着必须在某个时候创建这些依赖项。如果你不允许他们的消费者这样做,谁能创造他们?
这个问题的答案是:作文根。在大多数情况下,您应该将依赖项的创建从应用程序代码中移到组合根中。
以这个HomeController 为例,它被注入了一个用于创建IProductRepository 的工厂:
public class HomeController
{
private Func<IProductRepository> repoFactory;
public HomeController(Func<IProductRepository> repoFactory)
{
this.repoFactory = repoFactory;
}
public ViewResult Index()
{
var repository = this.repoFactory.Invoke();
var products = repository.GetFeaturedProducts();
return this.View(products);
}
}
从HomeController 的角度来看,需要考虑两个依赖项:Func<IProducerRepository> 和IProductRepository。这会导致复杂性。从HomeController 的角度来看,一个更简单的解决方案如下:
public class HomeController
{
private IProductRepository repository;
public HomeController(IProductRepository repository)
{
this.repository = repository;
}
public ViewResult Index()
{
var products = this.repository.GetFeaturedProducts();
return this.View(products);
}
}
虽然这简化了HomeController,但它解决了问题。但我们的想法是将这种复杂性转移到 Composition Root 中。
例如,在 Composition Root 中,您可以创建以下实现:
public class ProductRepositoryProxy : IProductRepository
{
private Func<IProductRepository> repoFactory;
public ProductRepositoryProxy(Func<IProductRepository> repoFactory)
{
this.repoFactory = repoFactory;
}
public IEnumerable<Product> GetFeaturedProducts()
{
var repository = this.repoFactory.Invoke();
return repository.GetFeaturedProducts();
}
}
这个ProductRepositoryProxy 包装了原始Func<IProductRepository> 并在每次调用时调用它。
这个ProductRepositoryProxy 假定任何IProductRepository 实现都是有状态的并且应该是短暂的。这很好,因为这些知识现在集中在 Composition Root 中。这简化了应用程序的其余部分,现在可以专注于业务决策,而不是依赖管理。
此描述是关于Abuse of Abstract Factories的较长讨论的简短版本,可在my book 的第6.2 节中找到。