【问题标题】:Conditional dependency resolver on run-time (.net Core)运行时的条件依赖解析器(.net Core)
【发布时间】:2019-09-02 14:01:54
【问题描述】:

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

interface IPaymentGateway { }

class PaymentGatewayFoo : IPaymentGateway { }
class PaymentGatewayBoo : IPaymentGateway { }

客户端请求有一个标识符,该标识符决定使用哪个实现:

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();
    }
}

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

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

【问题讨论】:

  • 你不能在这里使用条件注入,因为依赖的类型将在运行时确定,并且可以随着每个DoOrder 调用而改变。所以你需要注入这两个实现。
  • 不是依赖注入问题。看起来你需要Factory Design Pattern。看看

标签: c# dependency-injection .net-core


【解决方案1】:

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

1。使用工厂

public class OrderService
{
    private readonly IPaymentGatewayFactory _factory;

    public void DoOrder(bool isFoo)
    {
        IPaymentGateway service = _factory.Create(isFoo);
        this._service.Pay();
    }
}

工厂可以在哪里:

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

    public IPaymentGateway Create(bool isFoo) =>
        isFoo ? this.foo : this.boo;
}

优点和缺点:

2。使用适配器

public class OrderService
{
    private readonly IPayment _payment;

    public void DoOrder(bool isFoo)
    {
        _payment.Pay(isFoo);
    }
}

适配器可以在哪里:

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

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

        service.Pay();
    }
}

优点和缺点:

  • 这样做的好处是客户端只需要知道一个抽象。

替代实现

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

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

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

【讨论】:

    【解决方案2】:

    在尝试在这里实现出色的答案之后,看起来本机 DI .net 核心容器不支持注入可数(如 @armand 建议的 IPaymentGateway[]),所以我最终得到了一个基于 switch 的委托解析器案例(不是为了节省性能而进行反思):

    startup.cs

    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}");
        }
    });
    

    委托人:

    public delegate IPaymentGateway PaymentGatewayResolver(E_PaymentGatewayType paymentGatewayType);
    

    客户订单服务:

    private readonly PaymentGatewayResolver _paymentGatewayResolver;
    
    public OrderService(PaymentGatewayResolver paymentGatewayResolver)
    {
        this._paymentGatewayResolver = paymentGatewayResolver; 
    }
    
    public DoOrder(E_PaymentGatewayType paymentGatewayType)
    {
        IPaymentGateway paymentGateway = this._paymentGatewayResolver(paymentGatewayType);
        paymentGateway.Pay();
    }
    

    【讨论】:

    • 谢谢,这很有帮助
    【解决方案3】:

    当我遇到需要不同实现的案例时,这就是我处理这类问题的方法。

    public abstract class PaymentGateway : IPaymentGateway 
    {
        public long Identifier {get;}
        ...
    }
    
    public class PaymentGatewayFoo : PaymentGateway
    {
        public long Identifier => 1;
        ...
    }
    
    public class PaymentGatewayBoo : PaymentGateway
    {
        public long Identifier => 2;
        ...
    }
    
    public class PaymentGatewayProvider
    {
        private IPaymentGateway[] gateways;
        public PaymentGatewayProvider(IPaymentGateway[] gateways)
        {
             this.gateways = gateways;
        }
    
        public IPaymentGateway GetGateForClient(bool f) //This can be anything that you can use to identify which payment gateway you need
        {
             //As an example I would usually pass in a client or something that I can then use to identify which payment provider is mapped to a certain client, this way you can have hundreds of payment providers, but in your case you just had a boolean, so I used that
             if(f)
                  return gateways.First(f=> f.Identifier == 1);
             return gateways.First(f=> f.Identifier != 1);
        }
    }
    
    
    public class OrderService
    {
        private readonly PaymentGatewayProvider _provider;
    
        public void DoOrder(bool isFoo)
        {
            var service = _provider.GetGateForClient(isFoo);
            this._service.Pay();
        }
    }
    

    您识别支付提供商的方式可以是任何东西,我在这里仅使用 long 作为示例,但您可以将其映射到 enum 并使用它来进行识别,然后将枚举传递给 GetGateForClient获取正确支付网关的方法

    这可能无法直接编译,但应该让您大致了解如何解决问题。

    【讨论】:

    • 嘿,谢谢。 1. 如何在startup.cs中注册PaymentGatewayProvider? 2.PaymentGateway是否也要注册IPaymentGateway
    • 你应该可以在启动文件中给自己注册PaymentGatewayProvider,抱歉现在才回复,没有看到这个@ShaharShokrani的通知
    • 我遇到了解析错误。之后我只使用 IEnumarable 而不是 Array []
    猜你喜欢
    • 1970-01-01
    • 2018-12-24
    • 2018-07-13
    • 1970-01-01
    • 2019-04-13
    • 1970-01-01
    • 2022-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多