[英]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;
}
但是,這既不可擴展,也不合理。 有什么模式可以使我執行上述操作,而無需連續重構單例以從新的更窄的接口( ITypeA
, ITypeB
)派生,這些接口都實現了我所需的實際功能( 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 { }
這個想法是ServiceA
和ServiceB
都依賴於ISharedService
。 我們需要多次解決每個問題以進行測試:
IServiceA
,是否總是獲得相同的SharedService
實例? IServiceB
,是否總是獲得相同的SharedService
實例? IServiceA
和IServiceB
,是否獲得了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
,每個名稱都是一個單例。 然后,在注冊IServiceA
和ServiceB
我們指定要使用的已注冊組件的名稱。
IServiceA
和IServiceB
是瞬態的(未指定,但這是默認值)。
溫莎城堡
該解決方案使用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
,每個名稱都是一個單例。 (未指定,但這是默認設置。)然后,在注冊IServiceA
和ServiceB
我們指定要使用的已注冊組件的名稱。
在這兩種情況下,我都在創建ServiceCollection
而不對其進行任何處理。 關鍵是您仍然可以直接通過IServiceCollection
注冊類型,而不是通過Autofac或Windsor注冊類型。 因此,如果您注冊此:
serviceCollection.AddTransient<Whatever>();
...您可以解決Whatever
。 添加另一個容器並不意味着您現在必須向該容器注冊所有內容。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.