简体   繁体   English

用于将类型作为多个离散单例实例注入的模式

[英]Pattern for injecting a type as multiple discrete singleton instances

I am using asp.net core 2.2, I have a type which needs to be a singleton for the intended use case , however I require multiple discrete singleton instances of this same type such that they may be uniquely identifiable and injected where applicable. 我正在使用asp.net core 2.2,我的类型需要为预期的用例 ,但是我需要多个相同类型的离散单例实例 ,以便它们可以唯一标识和注入(如果适用)。

In other words, for use case A , one singleton must be used when ever functionality associated with use case A is required. 换句话说,对于用例A ,当需要与用例A相关联的功能时,必须使用一个单例。 For use case n , one singleton must be used when ever functionality associated with use case n is required. 对于用例n ,当需要与用例n相关联的功能时,必须使用一个单例。

The singleton is not semantically a singleton in the app domain, it is a singleton within all individual use cases. 从语义上讲,单例不是应用程序域中的单例,而是在所有单个用例中的单例。

A naive approach would be to refactor the interfaces so the following pattern could be used: 天真的方法是重构接口,因此可以使用以下模式:

using Microsoft.Extensions.DependencyInjection;

class Program
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<ITypeA, MySingleton>();
        services.AddSingleton<ITypeB, MySingleton>();
    }
}

public class MySingleton : ITypeA, ITypeB
{
}

public interface ITypeA : IMySingleton
{
}

public interface ITypeB : IMySingleton
{
}

public interface IMySingleton
{
}

Then to use a specific instance of the singleton type: 然后使用单例类型的特定实例:

class Foo
{
    private readonly IMySingleton instance;
    public Foo(ITypeA instance) => this.instance = instance;
}
class Bar
{
    private readonly IMySingleton instance;
    public Bar(ITypeB instance) => this.instance = instance;
}

However this is neither scalable or reasonable. 但是,这既不可扩展,也不合理。 What pattern exists that would allow me to perform the above without continuously refactoring the singleton to derive from new narrower interfaces ( ITypeA , ITypeB ) which all implement the actual functionality I need ( IMySingleton )? 有什么模式可以使我执行上述操作,而无需连续重构单例以从新的更窄的接口( ITypeAITypeB )派生,这些接口都实现了我所需的实际功能( IMySingleton )?

What pattern exists that would allow me to perform the above without continuously refactoring the singleton to derive from new narrower interfaces 存在什么模式可以使我执行上述操作,而无需不断重构单例以从新的更窄的接口派生

The Factory Pattern would. 工厂模式将。

Instead of injecting your target service, inject a Factory that returns the one the instances of your service. 而不是注入您的目标服务,而是注入一个Factory,该工厂返回服务实例。 EG 例如

interface IMyService
{
   . . .
}
interface IMyServiceFactory
{
    IMyService GetInstance(string Name);
}

This required creating a few extra classes and a unit test. 这需要创建一些额外的类和一个单元测试。 The test resolves services from the container and confirms that they were resolved and injected according to the specifications in your question. 该测试从容器中解析服务,并根据您的问题的规格确认已解决并注入了服务。 If we can configure the container so that the test passes, we've succeeded. 如果我们可以配置容器以使测试通过,那么我们就成功了。

public interface IServiceA
{
    ISharedService SharedService { get; }
}

public interface IServiceB
{
    ISharedService SharedService { get; }
}

public class ServiceA : IServiceA
{
    public ServiceA(ISharedService sharedService)
    {
        SharedService = sharedService;
    }

    public ISharedService SharedService { get; }
}

public class ServiceB : IServiceB
{
    public ServiceB(ISharedService sharedService)
    {
        SharedService = sharedService;
    }

    public ISharedService SharedService { get; }
}

public interface ISharedService { }

public class SharedService : ISharedService { }

The idea is that ServiceA and ServiceB both depend on ISharedService . 这个想法是ServiceAServiceB都依赖于ISharedService We need to resolve each of them multiple times to test: 我们需要多次解决每个问题以进行测试:

  • When we resolve IServiceA , do we always get the same instance of SharedService ? 解析IServiceA ,是否总是获得相同的SharedService实例?
  • When we resolve IServiceB , do we always get the same instance of SharedService ? 解析IServiceB ,是否总是获得相同的SharedService实例?
  • When we resolve IServiceA and IServiceB , do we get different instances of SharedService ? 当我们解析IServiceAIServiceB ,是否获得了SharedService不同实例?

Here's the outline of the unit test: 这是单元测试的概述:

public class DiscreteSingletonTests
{
    [TestMethod]
    public void ResolvesDiscreteSingletons()
    {
        var serviceProvider = GetServiceProvider();
        var resolvedA1 = serviceProvider.GetService<IServiceA>();
        var resolvedA2 = serviceProvider.GetService<IServiceA>();
        var resolvedB1 = serviceProvider.GetService<IServiceB>();
        var resolvedB2 = serviceProvider.GetService<IServiceB>();

        // Make sure we're resolving multiple instances of each. 
        // That way we'll know that the "discrete" singleton is really working.
        Assert.AreNotSame(resolvedA1, resolvedA2);
        Assert.AreNotSame(resolvedB1, resolvedB2);

        // Make sure that all instances of ServiceA or ServiceB receive
        // the same instance of SharedService.
        Assert.AreSame(resolvedA1.SharedService, resolvedA2.SharedService);
        Assert.AreSame(resolvedB1.SharedService, resolvedB2.SharedService);

        // ServiceA and ServiceB are not getting the same instance of SharedService.
        Assert.AreNotSame(resolvedA1.SharedService, resolvedB1.SharedService);
    }

    private IServiceProvider GetServiceProvider()
    {
        // This is the important part.
        // What goes here?
        // How can we register our services in such a way
        // that the unit test will pass?
    }
}

We can't do this with just IServiceCollection/IServiceProvider unless we do some really ugly stuff that I just don't want to do. 除非我们做一些我不想做的非常丑陋的事情,否则我们不能仅使用IServiceCollection/IServiceProvider来做到这一点。 Instead we can use different IoC containers, as recommended by this documentation : 相反,我们可以按照本文档的建议使用不同的IoC容器:

The built-in service container is meant to serve the needs of the framework and most consumer apps. 内置服务容器旨在满足框架和大多数消费者应用程序的需求。 We recommend using the built-in container unless you need a specific feature that it doesn't support. 我们建议您使用内置容器,除非您需要它不支持的特定功能。

In other words, if we want all the bells and whistles we have to use another container. 换句话说,如果我们想要所有的钟声,我们就必须使用另一个容器。 Here are some examples of how to do that: 以下是一些有关如何执行此操作的示例:


Autofac Autofac

This solution uses Autofac.Extensions.DependencyInjection . 此解决方案使用Autofac.Extensions.DependencyInjection You can alter it according to the example there which uses the Startup class. 您可以根据那里使用Startup类的示例进行更改。

private IServiceProvider GetServiceProvider()
{
    var serviceCollection = new ServiceCollection();
    var builder = new ContainerBuilder();
    builder.Populate(serviceCollection);

    builder.RegisterType<SharedService>().As<ISharedService>()
        .Named<ISharedService>("ForServiceA")
        .SingleInstance();
    builder.RegisterType<SharedService>().As<ISharedService>()
        .Named<ISharedService>("ForServiceB")
        .SingleInstance();
    builder.Register(ctx => new ServiceA(ctx.ResolveNamed<ISharedService>("ForServiceA")))
        .As<IServiceA>();
    builder.Register(ctx => new ServiceB(ctx.ResolveNamed<ISharedService>("ForServiceB")))
        .As<IServiceB>();

    var container = builder.Build();
    return new AutofacServiceProvider(container);
}

We're registering ISharedService twice with different names, each as a singleton. 我们正在使用不同的名称两次注册ISharedService ,每个名称都是一个单例。 Then, when registering IServiceA and ServiceB we're specifying the name of the registered component to use. 然后,在注册IServiceAServiceB我们指定要使用的已注册组件的名称。

IServiceA and IServiceB are transient (not specified, but it's the default). IServiceAIServiceB是瞬态的(未指定,但这是默认值)。


Castle Windsor 温莎城堡

This solution uses Castle.Windsor.MsDependencyInjection : 该解决方案使用Castle.Windsor.MsDependencyInjection

private IServiceProvider GetServiceProvider()
{
    var container = new WindsorContainer();
    var serviceCollection = new ServiceCollection();

    container.Register(
        Component.For<ISharedService, SharedService>().Named("ForServiceA"),
        Component.For<ISharedService, SharedService>().Named("ForServiceB"),
        Component.For<IServiceA, ServiceA>()
            .DependsOn(Dependency.OnComponent(typeof(ISharedService), "ForServiceA"))
            .LifestyleTransient(),
        Component.For<IServiceB, ServiceB>()
            .DependsOn(Dependency.OnComponent(typeof(ISharedService), "ForServiceB"))
            .LifestyleTransient()
    );
    return WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection);
}

We're registering ISharedService twice with different names, each as a singleton. 我们正在使用不同的名称两次注册ISharedService ,每个名称都是一个单例。 (It's not specified, but that's the default.) Then, when registering IServiceA and ServiceB we're specifying the name of the registered component to use. (未指定,但这是默认设置。)然后,在注册IServiceAServiceB我们指定要使用的已注册组件的名称。


In both cases I'm creating a ServiceCollection and not doing anything with it. 在这两种情况下,我都在创建ServiceCollection而不对其进行任何处理。 The point is that you can still register types directly with the IServiceCollection rather than through Autofac or Windsor. 关键是您仍然可以直接通过IServiceCollection注册类型,而不是通过Autofac或Windsor注册类型。 So if you registered this: 因此,如果您注册此:

serviceCollection.AddTransient<Whatever>();

...you can resolve Whatever . ...您可以解决Whatever Adding another container doesn't mean that you now have to register everything with that container. 添加另一个容器并不意味着您现在必须向该容器注册所有内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM