我正在使用asp.net core 2.2,对于预期的用例,我的类型必须为 ,但是我需要多个离散的singleton实例相同的类型,以便可以对其进行唯一标识和注入(如果适用)。
换句话说,对于用例 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
) ?
答案 0 :(得分:1)
存在什么模式可以使我执行上述操作,而无需不断重构单例以从新的较窄的接口派生
工厂模式会。
注入一个Factory而不是注入目标服务,而该工厂将返回您的服务实例。 EG
interface IMyService
{
. . .
}
interface IMyServiceFactory
{
IMyService GetInstance(string Name);
}
答案 1 :(得分:0)
这需要创建一些额外的类和一个单元测试。该测试从容器中解析服务,并根据您的问题的规格确认已解决并注入了服务。如果我们可以配置容器以使测试通过,那么我们就成功了。
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
来做到这一点。相反,我们可以按照this documentation的建议使用不同的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
。添加另一个容器并不意味着您现在必须在该容器中注册所有内容。