运行时条件依赖解析器 (.net Core)

Sha*_*ani 10 c# dependency-injection .net-core

我有两个类PaymentGatewayFooPaymentGatewayBoo它们都实现了一个公共接口IPaymentGateway

interface IPaymentGateway { }

class PaymentGatewayFoo : IPaymentGateway { }
class PaymentGatewayBoo : IPaymentGateway { }
Run Code Online (Sandbox Code Playgroud)

客户端请求有一个标识符,用于确定要使用的实现:

public class OrderService
{
    private readonly IPaymentGateway _service;

    public void DoOrder(bool isFoo)
    {
        if (isFoo)
            //this._service should be resolved with PaymentGatewayFoo
        else
            //this._service should be resolved with PaymentGatewayBoo

        this._service.Pay();
    }
}
Run Code Online (Sandbox Code Playgroud)

如何根据客户端在运行时的请求来解决正确的实现?

这个问题不是重复的,它相似但它是关于两个独立的控制器(即使答案表明代码甚至不需要条件依赖注入),在我的情况下,基于客户端属性的运行时需要条件依赖价值。

Ste*_*ven 8

这里有几个选项,但对我来说最明显的两个是使用工厂或适配器模式。

1.使用工厂

public class OrderService
{
    private readonly IPaymentGatewayFactory _factory;

    public void DoOrder(bool isFoo)
    {
        var service = _factory.Create(isFoo);
        this._service.Pay();
    }
}
Run Code Online (Sandbox Code Playgroud)

工厂可以在哪里:

public class PaymentGatewayFactory : IPaymentGatewayFactory 
{
    public PaymentGatewayFactory(PaymentGatewayFoo foo, PaymentGatewayBoo boo) {...}

    public IPaymentGateway Create(bool isFoo) =>
        isFoo ? this.foo : this.boo;
}
Run Code Online (Sandbox Code Playgroud)

使用工厂的缺点是消费者需要了解两个抽象:工厂和 IPaymentGateway。

2. 使用适配器

public class OrderService
{
    private readonly IPayment _payment;

    public void DoOrder(bool isFoo)
    {
        _payment.Pay(isFoo);
    }
}
Run Code Online (Sandbox Code Playgroud)

适配器可以在哪里:

public class PaymentAdapter : IPayment
{
    public PaymentAdapter(PaymentGatewayFoo foo, PaymentGatewayBoo boo) {...}

    public void Pay(bool isFoo)
    {
        var service = isFoo ? this.foo : this.boo;

        service.Pay();
    }
}
Run Code Online (Sandbox Code Playgroud)

这样做的好处是客户端只需要知道一个抽象。缺点是您需要第二个抽象。

替代实现

正如您所注意到的,在我的工厂和适配器中,实现是直接注入的。甚至不是通过它们的抽象,而是通过它们的具体类型。这可能看起来很奇怪,但只要适配器和工厂是应用程序入口点(也称为Composition Root)的一部分,这样做完全没问题。

但是可以使用其他更动态的选项,例如:

  • 注入Func<PaymentType, IPaymentGateway>委托来解析类型。
  • 注入一个Dictionary<PaymentType, IPaymentGateway>.
  • 注入 IPaymentGateway 实现的集合。
  • 注入容器本身
  • 使用动态过滤,正如 Armand 建议的那样,但请注意,这会导致您将Identifier属性添加到界面中,它仅出于技术原因而存在。没有消费者(适配器或工厂除外)对此标识符感兴趣。因此,它不属于接口。更好的解决方案是在 Composition Root 中解决这个问题,例如,可能通过使用属性标记实现


Sha*_*ani 6

在尝试在这里实现很好的答案之后,看起来原生 DI .net 核心容器不支持注入可数(就像IPaymentGateway[]@armand 建议的那样)所以我最终得到了一个基于 switch case 的委托解析器(不是在以节省性能):

启动文件

services.AddTransient<PaymentGatewayResolver>(serviceProvider => key =>
{
    switch (key)
    {
        case E_PaymentGatewayType.Foo:
            return serviceProvider.GetService<PaymentGatewayFoo>();
        case E_PaymentGatewayType.Boo:
            return serviceProvider.GetService<PaymentGatewayBoo>();
        case E_PaymentGatewayType.Undefined:
            default:
            throw new NotSupportedException($"PaymentGatewayRepositoryResolver, key: {key}");
    }
});
Run Code Online (Sandbox Code Playgroud)

代表:

public delegate IPaymentGateway PaymentGatewayResolver(E_PaymentGatewayType paymentGatewayType);
Run Code Online (Sandbox Code Playgroud)

客户订购服务:

private readonly PaymentGatewayResolver _paymentGatewayResolver;

public OrderService(PaymentGatewayResolver paymentGatewayResolver)
{
    this._paymentGatewayResolver = paymentGatewayResolver; 
}

public DoOrder(E_PaymentGatewayType paymentGatewayType)
{
    IPaymentGateway paymentGateway = this._paymentGatewayResolver(paymentGatewayType);
    paymentGateway.Pay();
}
Run Code Online (Sandbox Code Playgroud)