.NET 依赖注入中的动态实现选择

问题描述 投票:0回答:1

对于一个接口,我有两个不同的实现,我需要在依赖注入中注册它们,并且我需要有条件地检查在给定时刻要实例化哪个实现。 我已经使用了工厂模式和工厂委托,我只是想知道是否有更好的方法来实现这一点。

我会使用简单的对象来保持简单

I示例服务

    internal interface IExampleService
    {
        public ExampleMode Mode { get; }
        void Show();
    }

    internal enum ExampleMode
    {
        ExampleA,
        ExampleB
    }

示例A服务

        public ExampleMode Mode => ExampleMode.ExampleA;
        public ExampleAService()
        {
            Console.WriteLine("Creating Example A");
        }

        public void Dispose()
        {
            Console.WriteLine("Disposing Example A");
        }

        public void Show()
        {
            Console.WriteLine("Example A Show");
        }

示例B服务

        public ExampleMode Mode => ExampleMode.ExampleB;
        public ExampleBService()
        {
            Console.WriteLine("Creating Example B");
        }
        public void Dispose()
        {
            Console.WriteLine("Disposing Example B");
        }

        public void Show()
        {
            Console.WriteLine("Example B Show");
        }

实现依赖注入的条件实现选择的适当方法是什么。 我使用了两种不同的工厂模式实现,第一个采用 Ienumerable,因此服务由 DI 实例化,并遵循注册的指定服务生命周期,但我想避免仅为了选择一个而实例化所有实现的开销。 为了解决这个问题,我使用了另一个工厂变体,我手动实例化所需的服务,但现在我必须确保我手动处理这个对象,并且这些服务不再符合注册的服务生命周期。

示例服务工厂

    internal class ExampleServiceFactory : IExampleServiceFactory
    {
        private readonly IEnumerable<IExampleService> exampleServices;

        public ExampleServiceFactory(IEnumerable<IExampleService> exampleServices)
        {
            this.exampleServices = exampleServices;
        }

        public IExampleService GetExampleService(ExampleMode mode)
        {

           //Using DI to inject Ienumerable of services
            return exampleServices.FirstOrDefault(x => x.Mode == mode);



            //Manually instantiate Services
            //return mode switch
            //{
            //    ExampleMode.ExampleA => new ExampleAService(),
            //    ExampleMode.ExampleB => new ExampleBService(),
            //    _ => throw new NotSupportedException(),
            //};
        }
    }

向工厂注入 IServiceProvider 对我来说非常有意义,但这似乎是一种反模式

要避免的另一个服务定位器变体是注入一个在运行时解析依赖关系的工厂。这两种做法都混合了控制反转策略。

目前我正在使用工厂委托有条件地实例化所需的实现并将其传递给一个包装器,我可以自由地将其注入到其他页面、控制器等。

程序.cs

builder.Services.AddKeyedSingleton<IExampleService,ExampleAService>("ExampleA");
builder.Services.AddKeyedTransient<IExampleService,ExampleBService>("ExampleB");
builder.Services.AddTransient<IExampleServiceFactory,ExampleServiceFactory>();
var exampleMode = ConfigurationManager.AppSettings["Device"];

builder.Services.AddTransient<IGenericExampleService, GenericExampleService>((ctx) =>
{
    var service = Enum.Parse(typeof(ExampleMode), exampleMode) switch
    {
        ExampleMode.ExampleA => ctx.GetRequiredKeyedService<IExampleService>("ExampleA"),
        ExampleMode.ExampleB => ctx.GetRequiredKeyedService<IExampleService>("ExampleB"),
        _ => throw new InvalidOperationException()
    };
    return new GenericExampleService(service);
});
c# .net .net-core design-patterns dependency-injection
1个回答
0
投票

从上一个示例中可以清楚地看出,无需动态切换实现,因为您正在从配置加载

ExampleMode
,并且只有应用程序重新启动才能更改该值。

因此,我提出这个更简单的解决方案:

var mode =
    Enum.Parse(typeof(ExampleMode), ConfigurationManager.AppSettings["Device"]);

if (mode == ExampleMode.ExampleA)
{
    builder.Services.AddTransient<IExampleService, ExampleAService>();
}
else
{
    builder.Services.AddTransient<IExampleService, ExampleBService>();
}

因为在运行时总是只需要一个

IExampleService
实现,您只需注册该实现即可,让消费者直接依赖
IExampleService
。不需要工厂,不需要注射
IEnumerable<IExampleService>
.

© www.soinside.com 2019 - 2024. All rights reserved.