如何在Simple Injector中为工厂创建的对象解析装饰器

时间:2016-04-27 18:18:58

标签: c# decorator simple-injector

所以这将涉及到我展示了很多我的管道,但我会尽量将它保持在最低限度以保持这个问题的简单。

我的一个API端点依赖外部提供程序来完成调用。当用户向该端点发送查询时,他们可以指定在处理查询时他们希望我们使用哪个提供商,假设提供商是 Bing Google

所以我有一个IProvider接口和两个具体的实现BingProviderGoogleProvider(在我的真实API中,提供者接口实际上是一个通用的接口,但是我要保留泛型以避免让它变得比它更加混乱)。我需要根据查询中的字段解析正确的提供程序。 Simple Injector不允许注册同一接口的多个具体实现,因此我必须使用工厂;我创建一个看起来像这样的东西:

public class ProviderFactory
{
    private readonly Func<string, IProvider> _Selector;

    public ProviderFactory(Func<string, IProvider> selector)
    {
        this._Selector = selector;
    }

    public IProvider Get(string provider)
    {
        return this._Selector(provider);
    }
}

我通过这样的方式在容器中注册我的工厂:

container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider =>
    {
        switch (provider)
        {
            case "Bing":
                return container.GetInstance<BingProvider>()
            case "Google":
                return container.GetInstance<GoogleProvider>()
            default:
                throw new ArgumentOutOfRangeException("Unknown provider: " + provider);
        }
    }));

我测试了一下。有用。太棒了。

现在我需要为IProvider创建并注册多个装饰器。 IProvider的每个具体实现必须在容器解析它们时应用这些装饰器。为了这个例子,我可以说Decorator1Decorator2实现了IProvider。我用这样的容器注册它们:

container.RegisterDecorator(typeof(IProvider), typeof(Decorator1), Lifestyle.Singleton);
container.RegisterDecorator(typeof(IProvider), typeof(Decorator2), Lifestyle.Singleton);

这就是问题所在。当我的工厂解析BingProviderGoogleProvider的实例时,这正是我得到的。我想得到的是Decorator2的一个实例,它装饰了Decorator1的一个实例,它反过来装饰我请求的IProvider的具体实现。我认为这是因为我并没有特别要求容器解析IProvider的实例,而是要求它解决IProvider的具体实现。

我似乎已经把自己搞得一团糟,我不知道解决问题的最佳方法是什么。

修改

好的,在考虑了这一点后,我理解为什么装饰器永远不会被解决。以Decorator1BingProvider为例。 Decorator1BingProvider都实现了IProviderDecorator1没有实现BingProvider所以当我要求Simple Injector解析BingProvider的实例时,它是Decorator1甚至不可能给它一个IProvider的实例。我将不得不要求它以某种方式解析var bingProvider = Lifestyle.Singleton .CreateProducer<IProvider, BingProvider>(container); var googleProvider = Lifestyle.Singleton .CreateProducer<IProvider, GoogleProvider>(container); container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider => { switch (provider) { case "Bing": return bingProvider.GetInstance(); case "Google": return googleProvider.GetInstance(); default: throw new ArgumentOutOfRangeException("Unknown provider: " + provider); } })); 的实例,但在适当的位置给我正确的具体实现。

很好,我理解,但现在我不太确定如何继续。

更新

根据史蒂文的回答,我修改了我的工厂注册:

{{1}}

我的新问题是,当我运行单元测试以验证容器时,测试失败并显示如下错误(我不得不将此错误信息告诉我,以使其与我的示例匹配,希望这不会导致在翻译中丢失的任何东西):

  

配置无效。报告了以下诊断警告:   
   - [Torn Lifestyle] IProvider的注册映射到与IProvider注册相同的实现和生活方式。它们都映射到Decorator1(Singleton)。这将导致每个注册解析到另一个实例:每个注册都有自己的实例。   
   - [Torn Lifestyle] IProvider的注册映射到与IProvider注册相同的实现和生活方式。它们都映射到Decorator2(Singleton)。这将导致每个注册解析到另一个实例:每个注册都有自己的实例。   
  有关警告的详细信息,请参阅Error属性。请参阅https://simpleinjector.org/diagnostics如何解决问题以及如何压制个别警告。

1 个答案:

答案 0 :(得分:5)

  

Simple Injector不允许注册同一接口的多个具体实现

此声明不正确。实际上有多种方法可以做到这一点。我认为最常见的三种方法是:

  1. 使用条件注册
  2. 注册类型集合
  3. 手动创建InstanceProducer个实例。
  4. 特别是选项1和3似乎最适合您的情况,所以让我们从选项3开始:创建InstanceProducers

    // Create two providers for IProvider according to the required lifestyle.
    var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
    var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
    
    container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider => {
        switch (provider) {
            case "Bing": return bing.GetInstance();
            case "Google": return google.GetInstance();
            default: throw new ArgumentOutOfRangeException();
        }
    }));
    

    在这里,我们创建了两个InstanceProducer个实例,每个IProvider一个。这里的重要部分是为IProvider抽象创建一个生成器,因为这允许应用IProvider的装饰器。

    或者,您可以选择在switch内移动case - ProviderFactory语句,并为其提供两个单独的代理人;每个提供商一个。例如:

    public ProviderFactory(Func<IProvider> bingProvider, Func<IProvider> googleProvider) { .. }
    
    public IProvider Get(string provider) {
        switch (provider) {
            case "Bing": return bingProvider();
            case "Google": return googleProvider();
            default: throw new ArgumentOutOfRangeException();
        }
    }
    

    注册看起来非常类似于前一个:

    var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
    var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
    
    container.RegisterSingleton<ProviderFactory>(new ProviderFactory(
        bingProvider: bing.GetInstance,
        googleProvider: google.GetInstance));
    

    而不是将Func<T>个代表注入工厂,根据您的需要,您可以直接注入IProvider。这意味着您的构造函数将如下所示:

    public ProviderFactory(IProvider bing, IProvider google) { ... }
    

    现在,您可以在IProvider上使用条件注册来消除构造函数参数的歧义:

    container.RegisterSingleton<ProviderFactory>();
    container.RegisterConditional<IProvider, BingProvider>(
        c => c.Consumer.Target.Name == "bing");
    container.RegisterConditional<IProvider, GoogleProvider>(
        c => c.Consumer.Target.Name == "google");
    

    这样做的好处是你不会延迟构建对象图;当工厂的消费者得到解决时,所有提供者都会直接注入。

    或者,您可能还想尝试使用调度程序抽象替换工厂的设计。工厂抽象通常不是消费者最简单的解决方案,因为他们现在必须处理工厂类型和返回的服务抽象。另一方面,调度程序或处理器为消费者提供单一抽象。这使得消费者(及其单元测试)通常更简单。

    这样的调度程序看起来很像IProvider接口本身,但在其实例方法中添加了string provider参数。例如:

    interface IProviderDispatcher {
        void DoSomething(string provider, ProviderData data);
    }
    

    调度员的实施可能如下所示:

    public ProviderDispatcher(IProvider bing, IProvider google) { .. }
    
    public void DoSomething(string provider, ProviderData data) {
        this.Get(provider).DoSomething(data);
    }
    
    private IProvider Get(string provider) {
        switch (provider) {
            case "Bing": return this.bing;
            case "Google": return this.google;
            default: throw new ArgumentOutOfRangeException();
        }
    }
    

    调度员的解决方案可以与工厂相同,但现在我们隐藏了消费者的额外步骤。

    如果我们可以完全删除IProviderDispatcher抽象,那就更好了,但这只有在string provider运行时数据是请求期间可用的上下文数据时才有可能。在这种情况下,我们可以执行以下操作:

    interface IProviderContext {
        string CurrentProvider { get; }
    }
    

    而不是单独的提供者抽象,我们可以在IProvider上实现代理实现:

    class ProviderDispatcherProxy : IProvider {
        public ProviderDispatcherProxy(Func<IProvider> bingProvider, 
            Func<IProvider> googleProvider,
            IProviderContext providerContext) { ... }
    
        void IProvider.DoSomething(ProviderData data) {
            // Dispatch to the correct provider
            this.GetCurrentProvider.DoSomething(data);
        }
    
        private IProvider GetCurrentProvider() =>
            switch (this.providerContext.CurrentProvider) {
                case "Bing": return this.bingProvider();
                case "Google": return this.googleProvider();
                default: throw new ArgumentOutOfRangeException();
            }
        };
    }
    
    class AspNetProviderContext : IProviderContext {
        public CurrentProvider => HttpContext.Current.Request.QueryString["provider"];
    }
    

    同样,在内部,它仍然像以前一样,但现在,因为提供者价值是我们可以从可用的环境上下文(HttpContext.Current)解决的,我们将能够让消费者直接与IProvider合作。您可以按如下方式注册:

    container.RegisterSingleton<IProviderContext>(new AspNetProviderContext());
    
    var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
    var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
    container.RegisterSingleton<IProvider>(new ProviderDispatcherProxy(
        bingProvider: bing.GetInstance,
        googleProvider: google.GetInstance));
    

    现在您可以简单地向您的消费者注入IProvider,并在后台自动发送调度。