Jas*_*oyd 3 c# decorator simple-injector
所以这将涉及到我展示了很多我的管道,但我会尽量将它保持在最低限度,以保持这个问题的简单.
我的一个API端点依赖外部提供程序来完成调用.当用户向该端点发送查询时,他们可以指定在处理查询时他们希望我们使用哪个提供商,假设提供商是Bing和Google.
所以我有一个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);
}
}
Run Code Online (Sandbox Code Playgroud)
我通过这样的方式在容器中注册我的工厂:
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);
}
}));
Run Code Online (Sandbox Code Playgroud)
我测试一下 有用.太棒了.
现在我需要为我创建和注册多个装饰器IProvider.每个具体实现IProvider必须在容器解析它们时应用这些装饰器.为了这个例子,我可以说我有Decorator1和Decorator2哪个实现IProvider.我用这样的容器注册它们:
container.RegisterDecorator(typeof(IProvider), typeof(Decorator1), Lifestyle.Singleton);
container.RegisterDecorator(typeof(IProvider), typeof(Decorator2), Lifestyle.Singleton);
Run Code Online (Sandbox Code Playgroud)
这就是问题所在.当我的工厂解析一个实例BingProvider或者GoogleProvider我正好得到的实例时.我想要得到的是一个实例,Decorator2它装饰一个实例,Decorator1然后依次装饰IProvider我要求的具体实现.我假设这是因为我没有具体要求容器解析一个实例,IProvider而是我要求它解决一个具体的实现IProvider.
我似乎已经把自己纠结在这里,我不确定解决它的最佳方法是什么.
编辑
好的,在考虑了这个之后,我理解为什么装饰器永远不会被解决.以Decorator1和BingProvider为实例.双方Decorator1并BingProvider实施IProvider,但Decorator1并没有实现BingProvider,所以当我问简单的注射器来解决一个实例BingProvider它甚至不能为它给我的一个实例Decorator1.我将不得不要求它以某种方式解决一个实例,IProvider但给我正确的具体实现与装饰器到位.
很好,我理解,但现在我不太确定如何继续.
更新
根据史蒂文的回答,我修改了我的工厂注册,如下所示:
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);
}
}));
Run Code Online (Sandbox Code Playgroud)
我的新问题是,当我运行我的单元测试以验证容器时,测试失败并显示如下错误(我不得不将此错误信息告诉我,以使其与我的示例匹配,希望这不会导致任何问题丢失在翻译中):
配置无效.报告了以下诊断警告:
- [Torn Lifestyle] IProvider的注册映射到与IProvider注册相同的实施和生活方式.它们都映射到Decorator1(Singleton).这将导致每个注册解析到另一个实例:每个注册都有自己的实例.
- [Torn Lifestyle] IProvider的注册映射到与IProvider注册相同的实现和生活方式.它们都映射到Decorator2(Singleton).这将导致每个注册解析到另一个实例:每个注册都有自己的实例.
有关警告的详细信息,请参阅Error属性.请参阅https://simpleinjector.org/diagnostics如何解决问题以及如何抑制个别警告.
Simple Injector不允许注册同一接口的多个具体实现
这个说法不正确.实际上有多种方法可以做到这一点.我认为最常见的三种方法是:
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();
}
}));
Run Code Online (Sandbox Code Playgroud)
这里我们创建两个InstanceProducer实例,每个实例一个IProvider.这里的重要部分是为IProvider抽象创建一个生成器,因为这允许应用装饰器IProvider.
或者,您可以选择在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();
}
}
Run Code Online (Sandbox Code Playgroud)
注册看起来非常类似于前一个:
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));
Run Code Online (Sandbox Code Playgroud)
而不是将Func<T>代表注入工厂,根据您的需要,您可以IProvider直接注入.这意味着您的构造函数将如下所示:
public ProviderFactory(IProvider bing, IProvider google) { ... }
Run Code Online (Sandbox Code Playgroud)
现在,您可以使用条件注册IProvider来消除构造函数参数的歧义:
container.RegisterSingleton<ProviderFactory>();
container.RegisterConditional<IProvider, BingProvider>(
c => c.Consumer.Target.Name == "bing");
container.RegisterConditional<IProvider, GoogleProvider>(
c => c.Consumer.Target.Name == "google");
Run Code Online (Sandbox Code Playgroud)
这样做的好处是你不会延迟构建对象图; 当工厂的消费者得到解决时,所有提供者都会直接注入.
或者,您可能还想尝试使用调度程序抽象替换工厂的设计.工厂抽象通常不是消费者最简单的解决方案,因为他们现在必须处理工厂类型和返回的服务抽象.另一方面,调度程序或处理器为消费者提供单一抽象.这使得消费者(及其单元测试)通常更简单.
这样的调度程序看起来很像IProvider接口本身,但是string provider为其实例方法添加了一个参数.例如:
interface IProviderDispatcher {
void DoSomething(string provider, ProviderData data);
}
Run Code Online (Sandbox Code Playgroud)
调度员的实施可能如下所示:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
调度员的解决方案可以与工厂相同,但现在我们隐藏了消费者的额外步骤.
如果我们可以IProviderDispatcher完全删除抽象,那就更好了,但这只有在string provider运行时数据是请求期间可用的上下文数据时才有可能.在这种情况下,我们可以执行以下操作:
interface IProviderContext {
string CurrentProvider { get; }
}
Run Code Online (Sandbox Code Playgroud)
而不是单独的提供者抽象,我们可以有一个代理实现IProvider:
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"];
}
Run Code Online (Sandbox Code Playgroud)
同样,在内部它仍然像以前一样,但现在,因为提供者价值是我们可以从可用的环境上下文(HttpContext.Current)中解决的,我们将能够让消费者IProvider直接使用.您可以按如下方式注册:
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));
Run Code Online (Sandbox Code Playgroud)
现在您可以简单地IProvider向您的消费者注入一个,并在后台自动发送调度.
| 归档时间: |
|
| 查看次数: |
438 次 |
| 最近记录: |