簡體   English   中英

用於將類型作為多個離散單例實例注入的模式

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

我正在使用asp.net core 2.2,我的類型需要為預期的用例 ,但是我需要多個相同類型的離散單例實例 ,以便它們可以唯一標識和注入(如果適用)。

換句話說,對於用例A ,當需要與用例A相關聯的功能時,必須使用一個單例。 對於用例n ,當需要與用例n相關聯的功能時,必須使用一個單例。

從語義上講,單例不是應用程序域中的單例,而是在所有單個用例中的單例。

天真的方法是重構接口,因此可以使用以下模式:

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
{
}

然后使用單例類型的特定實例:

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

但是,這既不可擴展,也不合理。 有什么模式可以使我執行上述操作,而無需連續重構單例以從新的更窄的接口( ITypeAITypeB )派生,這些接口都實現了我所需的實際功能( IMySingleton )?

存在什么模式可以使我執行上述操作,而無需不斷重構單例以從新的更窄的接口派生

工廠模式將。

而不是注入您的目標服務,而是注入一個Factory,該工廠返回服務實例。 例如

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

這需要創建一些額外的類和一個單元測試。 該測試從容器中解析服務,並根據您的問題的規格確認已解決並注入了服務。 如果我們可以配置容器以使測試通過,那么我們就成功了。

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 { }

這個想法是ServiceAServiceB都依賴於ISharedService 我們需要多次解決每個問題以進行測試:

  • 解析IServiceA ,是否總是獲得相同的SharedService實例?
  • 解析IServiceB ,是否總是獲得相同的SharedService實例?
  • 當我們解析IServiceAIServiceB ,是否獲得了SharedService不同實例?

這是單元測試的概述:

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?
    }
}

除非我們做一些我不想做的非常丑陋的事情,否則我們不能僅使用IServiceCollection/IServiceProvider來做到這一點。 相反,我們可以按照本文檔的建議使用不同的IoC容器:

內置服務容器旨在滿足框架和大多數消費者應用程序的需求。 我們建議您使用內置容器,除非您需要它不支持的特定功能。

換句話說,如果我們想要所有的鍾聲,我們就必須使用另一個容器。 以下是一些有關如何執行此操作的示例:


Autofac

此解決方案使用Autofac.Extensions.DependencyInjection 您可以根據那里使用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);
}

我們正在使用不同的名稱兩次注冊ISharedService ,每個名稱都是一個單例。 然后,在注冊IServiceAServiceB我們指定要使用的已注冊組件的名稱。

IServiceAIServiceB是瞬態的(未指定,但這是默認值)。


溫莎城堡

該解決方案使用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);
}

我們正在使用不同的名稱兩次注冊ISharedService ,每個名稱都是一個單例。 (未指定,但這是默認設置。)然后,在注冊IServiceAServiceB我們指定要使用的已注冊組件的名稱。


在這兩種情況下,我都在創建ServiceCollection而不對其進行任何處理。 關鍵是您仍然可以直接通過IServiceCollection注冊類型,而不是通過Autofac或Windsor注冊類型。 因此,如果您注冊此:

serviceCollection.AddTransient<Whatever>();

...您可以解決Whatever 添加另一個容器並不意味着您現在必須向該容器注冊所有內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM