ASP.NET: 为什么避免在工厂中使用 GetService?
ASP.NET: why avoid using GetService in a factory?
最近我找到了这篇文章:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1。它是关于 ASP.NET 核心应用程序中的依赖注入。里面有个推荐,我不是很懂:
“避免使用 GetService 注入在运行时解决依赖关系的工厂。”
问题是这种方法可能有用。假设我们必须在运行时创建 N 个 Something 实例。所以我们可以在 Client class 中注入一些工厂,它将使用该工厂创建 Something。但是,如果 Something 也有一些依赖项需要解决怎么办?这意味着我们需要在工厂中使用 GetService 来解析所有依赖项。但是以这种方式我们将使用这种我们应该避免的反模式。
所以我的问题是:为什么我们应该避免显式调用 GetService,我们如何实现类似的东西,但没有 GetService?
希望我理解正确。
假设您有 10 个 类 实现了 ISomething
。您可以像这样在您的 ServiceCollection 中注册它们:
services.AddTransient<ISomething, Something1>();
services.AddTransient<ISomething, Something2>();
通过这种方式,您的实现也可以具有依赖项,但它们是通过您的 DI 容器注入的。
如果您想在 class 中注入所有 ISomething 实现,只需通过构造函数注入它们。
public class SomethingConsumer
{
public SomethingConsumer(IEnumerable<ISomthing> somethingList)
{
}
...
}
Microsoft 文档对此的建议并没有错,但由于缺乏大量上下文,因此过于简单化。我并不是真的责怪作家,因为可以写出关于这个主题的整本书(exactly what I did)。
有很多事情要考虑。要理解的第一个重要概念是 Composition Root。它是应用程序的一部分,其中注册依赖项并构建对象图。
另一个重要的概念是Service Locator anti-pattern。正如 anti-pattern 标签所暗示的那样,这是一个应该始终避免的模式。当您允许代码(位于组合根之外)解析 any 服务时,您正在应用此反模式。例如,当您将 Func<Type, object>
委托注入消费者时,或者当您将 IServiceProvider
注入消费者时,就会出现这种情况。它的 GetService
方法有那个委托。
另一方面,对于存在于内部组合根的代码,没有服务定位器的概念;这种反模式在那里不存在。那是因为 Composition Root 的主要功能是构造类型。它已经直接依赖于 DI 容器。 Composition Root 有望解析服务。
然而,重要的是要注意,具有特定委托与服务定位器不同。例如,如果你注入一个 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 中,您可以创建以下实现:
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 中。这简化了应用程序的其余部分,现在可以专注于业务决策,而不是依赖管理。
此描述是关于 滥用抽象工厂 的较长讨论的简短版本,可在 my book 的第 6.2 节中找到。
最近我找到了这篇文章:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1。它是关于 ASP.NET 核心应用程序中的依赖注入。里面有个推荐,我不是很懂:
“避免使用 GetService 注入在运行时解决依赖关系的工厂。”
问题是这种方法可能有用。假设我们必须在运行时创建 N 个 Something 实例。所以我们可以在 Client class 中注入一些工厂,它将使用该工厂创建 Something。但是,如果 Something 也有一些依赖项需要解决怎么办?这意味着我们需要在工厂中使用 GetService 来解析所有依赖项。但是以这种方式我们将使用这种我们应该避免的反模式。
所以我的问题是:为什么我们应该避免显式调用 GetService,我们如何实现类似的东西,但没有 GetService?
希望我理解正确。
假设您有 10 个 类 实现了 ISomething
。您可以像这样在您的 ServiceCollection 中注册它们:
services.AddTransient<ISomething, Something1>();
services.AddTransient<ISomething, Something2>();
通过这种方式,您的实现也可以具有依赖项,但它们是通过您的 DI 容器注入的。
如果您想在 class 中注入所有 ISomething 实现,只需通过构造函数注入它们。
public class SomethingConsumer
{
public SomethingConsumer(IEnumerable<ISomthing> somethingList)
{
}
...
}
Microsoft 文档对此的建议并没有错,但由于缺乏大量上下文,因此过于简单化。我并不是真的责怪作家,因为可以写出关于这个主题的整本书(exactly what I did)。
有很多事情要考虑。要理解的第一个重要概念是 Composition Root。它是应用程序的一部分,其中注册依赖项并构建对象图。
另一个重要的概念是Service Locator anti-pattern。正如 anti-pattern 标签所暗示的那样,这是一个应该始终避免的模式。当您允许代码(位于组合根之外)解析 any 服务时,您正在应用此反模式。例如,当您将 Func<Type, object>
委托注入消费者时,或者当您将 IServiceProvider
注入消费者时,就会出现这种情况。它的 GetService
方法有那个委托。
另一方面,对于存在于内部组合根的代码,没有服务定位器的概念;这种反模式在那里不存在。那是因为 Composition Root 的主要功能是构造类型。它已经直接依赖于 DI 容器。 Composition Root 有望解析服务。
然而,重要的是要注意,具有特定委托与服务定位器不同。例如,如果你注入一个 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 中,您可以创建以下实现:
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 中。这简化了应用程序的其余部分,现在可以专注于业务决策,而不是依赖管理。
此描述是关于 滥用抽象工厂 的较长讨论的简短版本,可在 my book 的第 6.2 节中找到。