代码之家  ›  专栏  ›  技术社区  ›  Jason Boyd

如何在Simple Injector中解析工厂创建的对象的修饰符

  •  3
  • Jason Boyd  · 技术社区  · 10 年前

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

    我的一个API端点依赖于外部提供者来完成调用。当用户向该端点发送查询时,他们可以指定他们希望我们在处理查询时使用的提供者,假设提供者是 必应Bing 谷歌 .

    所以我有一个 IProvider 接口和两个具体实现 BingProvider GoogleProvider (在我真正的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);
            }
        }));
    

    我测试了一下。它起作用了。好极了

    现在,我需要为我的 I提供商 .每项具体实施 I提供商 容器解析这些修饰符时,必须应用这些修饰器。为了这个例子,让我们假设 Decorator1 Decorator2 哪个机具 I提供商 .我将它们注册到容器中,如下所示:

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

    这就是问题所在。当我的工厂解决 Bing提供商 谷歌提供商 这正是我得到的。我想得到的是 装饰工2 它装饰了 装饰工1 这反过来又修饰了 I提供商 我请求过。我认为这是因为我没有特别要求容器解析 I提供商 而是我要求它解决具体实施 I提供商 .

    我似乎把自己搞得一团糟,我不知道最好的解决办法是什么。

    编辑

    好吧,再考虑一下,我明白了为什么装饰师永远不会解决这个问题。拿 装饰工1 Bing提供商 例如。二者都 装饰工1 Bing提供商 使生效 I提供商 但是 装饰工1 未实现 Bing提供商 因此,当我要求Simple Injector解析 Bing提供商 它甚至不可能给我一个 装饰工1 。我必须要求它以某种方式解决 I提供商 但请给我正确的具体实现,使装饰器就位。

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

    使现代化

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

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

    我的新问题是,当我运行单元测试以验证容器时,测试失败,错误如下(我必须修改此错误消息以使其与我的示例匹配,希望这不会导致在翻译中丢失任何内容):

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

    1 回复  |  直到 10 年前
        1
  •  5
  •   Steven    10 年前

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

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

    1. 使用条件注册
    2. 注册类型集合
    3. 手动创建 InstanceProducer 实例。

    特别是选项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 。这里的重要部分是为 I提供商 抽象,因为这允许 I提供商 待应用。

    或者,您可以选择移动 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> 代表进入工厂,根据您的需要,它可能适合您注入 I提供商 直接地这意味着您的构造函数将如下所示:

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

    现在您可以在上使用条件注册 I提供商 要消除构造函数参数的歧义,请执行以下操作:

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

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

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

    这样的调度员看起来很像 I提供商 接口本身,但添加了 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 完全抽象,但只有在 字符串提供程序 运行时数据是请求期间可用的上下文数据。在这种情况下,我们可以执行以下操作:

    interface IProviderContext {
        string CurrentProvider { get; }
    }
    

    我们可以在 I提供商 :

    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 ),我们将能够让消费者与 I提供商 直接地您可以按如下方式注册:

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

    现在您可以简单地注入 I提供商 你的消费者和调度将自动在后台为你发生。