服务定位器反模式:对于工厂注册来说是可行的。

4
我正在创建一个REST框架,想知道是否可以为服务提供商提供访问权限,因为它将用于工厂。我有一个通用的服务,其中包含在创建和更新时触发的事件。
public event Func<object, TDTO, Task> CreateEvent;

protected virtual async Task OnCreated(TDTO dto)
{
    await ServiceEventHandlerRule.OnServiceEvent(CreateEvent, this, dto);
}

public async Task<TDTO> Create(TDTO dto)
{
    var entity = this.DtoToEntity(dto, new TEntity());
    this._context.Set<TEntity>().Add(entity);

    dto = await this.SaveEntity(entity);
    await OnCreated(dto);
    return dto;
}

我正在工厂中为我的服务注册事件,目前我暴露了一个 Func<IServiceProvider, Object, TDto> 事件注册,它看起来像这样,在我的 startup.cs 文件中:
var serviceConfig = new ServiceConfiuration<User, UserDTO>();
serviceConfig.EventMapping.AddCreateEvent(async (sp, obj, dto) => {
    var hubTaskQueue = sp.GetService<IHubServiceTaskQueue>();
    hubTaskQueue.QueueCreate<THub, TDTO>(dto);
});
services.AddScopedRestService(serviceConfig);

用户将需要访问一些额外的依赖项来进行他们的事件,因此我已经给予他们访问IServiceProvider的权限。
使用服务定位模式进行注册的缺点是什么?
我应该掩盖IServiceProvider并创建一些泛型重载来自动解决它们的依赖关系吗?
1个回答

6
《依赖注入原理、实践与模式》中,我们将Service Locator定义为:

服务定位器向应用程序组件提供访问无限制的易变依赖项,这些组件在组合根之外。 [第5.2章]

组合根是应用程序中的一个单一逻辑位置,在该位置,模块被组合在一起。您可以在本书摘录中找到有关组合根的详细描述。
关于您的问题,Service Locator定义中重要的部分是:“组合根之外”。换句话说,从组合根内部访问无限制的易变依赖项是可以的,并且不是Service Locator反模式的实现。然而,从组合根之外访问它们是反模式。
为了回答你的问题,只要你的工厂实现是组合根的一部分,你就可以安全地让你的工厂依赖于容器或解决无界依赖关系的抽象。
你可以通过将工厂的抽象放置在应用程序逻辑附近来实现这一点,同时在应用程序的启动程序集中创建工厂实现。
如果依赖于IServiceProvider的代码不是组合根的一部分,则使用服务定位器反模式,这应该被防止。服务定位器反模式的主要问题是:
- 模块将服务定位器作为冗余依赖项带上。 - 模块使其依赖项变得不明显。这使得这样的模块更难测试,掩盖了类可能过于复杂的事实,并使验证依赖关系图的正确性变得更加困难,因为依赖关系是惰性获取的。
相反,你应该让那些用户将其依赖项定义为构造函数参数。

这是部分位于组合根中的内容,我允许用户在启动时动态配置服务,我的主要目标是让他们避免首先创建类。 - johnny 5
我猜这可能不太好,因为用户可以访问它。现在,我将只创建一些额外的通用方法,其通用参数将只是要从服务提供程序中获取的依赖项,以使使用仅限于库代码的一部分得到抽象化。 - johnny 5
您的组合根是您组合对象图的逻辑位置。如果您在配置中定义了抽象和实现之间的映射,则该配置文件是组合根的一部分。 - Steven
请注意,您应该防止在组合根外部调用任何可以解析任何依赖项的服务。即使在库内部或主应用程序中使用Func<Type, object>也可以成为服务定位器。无论是在库还是在主应用程序中,都没有关系。这仍然是服务定位器。 - Steven
1
我不是很清楚你的框架是如何工作的,但阅读Mark Seemann关于如何构建DI友好型框架的建议会很有帮助。不要使用提供依赖项的Func<T>方法来定义事件,而是定义一个工厂抽象,用户可以重写以拦截根类型的创建。在你的情况下,你的根类型似乎是事件处理程序?然后用户可以使用常见的实践,比如构造函数注入,来定义他们的事件处理程序,事件处理程序工厂可以解析它们的依赖项。 - Steven
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接