IoC容器中的命名服务——一个坏主意

本文关键字:一个 IoC 服务 | 更新日期: 2023-09-27 18:01:48

在组合容器时使用服务键(或'命名服务')似乎不是一个好主意。

使用命名服务需要我们用匹配的键注释构造函数参数(因此与容器耦合),或者为每个服务执行额外的连接(因此失去了容器的大量自动化)。

例如,我目前有以下接口,由以下类实现:

  • IListSerializer
    • CheckboxListSerializer
    • TreeViewListSerializer

我还有无数依赖于这两个类中的一个或两个的类。然而,我想我应该引用IListSerializer作为我的依赖,而不是实现。这意味着我必须使用键/名称来区分它们,这就是它开始变得丑陋的地方。

我可以看到我的选项是以下之一:

  • 用键注释构造函数参数(依赖项)。与IoC容器耦合。
  • 在组合根目录下手动布线。增加重复膨胀。
  • 引用类而不是接口。似乎只是为了满足IoC容器。

有什么建议吗?

IoC容器中的命名服务——一个坏主意

一般来说,在为IoC设计组件和服务时,Liskov替代原则是一个非常有用的指南。如果一个服务的两个实现不能在运行时互换使用,那么这个服务就太一般化了,没有意义。在这种情况下,我会考虑使用IListSerializer<T>之类的东西,如果这是一个选项的话。

然而,如果你想使用命名服务,这是很容易和无干扰的Autofac设置。

首先,用它的名字注册每个序列化器:

builder.RegisterType<CheckBoxListSerializer>()
    .Named<IListSerializer>("checkBoxSerializer");
builder.RegisterType<TreeViewListSerializer>()
    .Named<IListSerializer>("treeViewSerializer");

然后,添加一个全局可用的参数,该参数使用构造函数参数名来选择正确的实现。我们可以用一个模块来完成:

class NamedParameterResolutionModule<TService> : Module
{
    Parameter _attachedParameter = new ResolvedParameter(
        (pi, c) => pi.ParameterType == typeof(TService),
        (pi, c) => c.ResolveNamed<TService>(pi.Name));
    protected override void AttachToComponentRegistration(
        IComponentRegistry registry,
        IComponentRegistration registration)
    {
        registration.Preparing += (s, e) => {
            e.Parameters = new[] { _attachedParameter }.Contact(e.Parameters);
        };
    }
}

像这样注册模块:

builder.RegisterModule<NamedParameterResolutionModule<IListSerializer>>();

组件将根据构造函数参数名获得一个序列化器:

class SomeComponent : ...
{
    public SomeComponent(IListSerializer checkBoxSerializer) { ...
}