如何使用.NET Core DI装饰自定义工厂创建的对象



假设我有一个工厂类负责构建某个服务的实例,该服务的构造函数参数只能在运行时解析,有没有办法利用容器驱动的装饰?

考虑以下依赖于仅在运行时定义的参数的类:

interface IFooService
{
void DoServicyStuff();
}
class MyFooService : IFooService
{
public MyFooService(string somePeskyRuntimeArgument)
{
this.peskyValue = somePeskyRuntimeArgument;
}
public void DoServicyStuff()
{
// do some stuff here with the peskyValue...
}
}

由于该值只能在运行时提供,因此我们需要从构造函数注入转移到方法级参数传递。这通常是使用这样的工厂实现来实现的:

interface IFooServiceFactory
{
IFooService CreateService(string heyItsNowAMethodLevelPeskyParameter);
}
class FooServiceFactory : IFooServiceFactory
{
public IFooService CreateService(string heyItsNowAMethodLevelPeskyParameter)
{
return new MyFooService(heyItsNowAMethodLevelPeskyParameter);
}
}

虽然如果目的只是抽象掉服务的构造,这很好,但装饰IFooService实例会带来挑战。

对于不涉及运行时参数的场景,这可以通过进入容器为我们提供服务来轻松实现

class FooServiceFactory : IFooServiceFactory
{
private readonly IServiceProvider serviceProvider;
public FooServiceFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider
}
public IFooService CreateService(string heyItsNowAMethodLevelPeskyParameter)
{
return this.serviceProvider.GetRequiredInstance<IFooService>();
}
}
...
services
.AddTransient<IFooService, MyFooService>()
.AddTransient<IFooServiceFactory, FooServiceFactory>()
.Decorate<IFooService, LoggingFooService>();

但由于MyFooService以一个基元值为自变量,我们不能依赖GetRequiredService<T>来获取实例,因为它将无法找到";CCD_ 4"的注册;建造混凝土类时。

类似地,将工厂更改为依赖ActivatorUtilities.CreateInstance.CreateFactory方法将最终创建对象,同时完全忽略容器注册,从而使我们没有任何装饰器。

我知道我至少有两个手动装饰对象的选项,即:

  1. 使用工厂本身手动创建装饰器:
public IFooService CreateService(string heyItsNowAMethodLevelPeskyParameter)
{
return new LoggingService(
new MyFooService(heyItsNowAMethodLevelPeskyParameter));
}
  1. 在创建实例后使用工厂装饰器注入装饰器:
abstract class FooServiceFactoryDecorator : IFooServiceFactory
{
private readonly IFooServiceFactory fooServiceFactory;
protected FooServiceFactory(IFooServiceFactory fooServiceFactory)
{
this.fooServiceFactory = fooServiceFactory;
}
public virtual IFooService CreateService(string heyItsNowAMethodLevelPeskyParameter)
{
return this.fooServiceFactory.CreateService(heyItsNowAMethodLevelPeskyParameter);
}
}
class LoggingFooServiceFactory : FooServiceFactoryDecorator
{
private readonly IFooServiceFactory fooServiceFactory;
public FooServiceFactory(IFooServiceFactory fooServiceFactory)
{
this.fooServiceFactory = fooServiceFactory;
}
public override IFooService CreateService(string heyItsNowAMethodLevelPeskyParameter)
{
return new LoggingFooService(
this.fooServiceFactory.CreateService(heyItsNowAMethodLevelPeskyParameter));
}
}
...
services
.AddTransient<IFooServiceFactory, FooServiceFactory>()
.Decorate<IFooServiceFactory, LoggingFooServiceFactory>()

这两者都不允许我在服务接口的顶部直接使用.Decorate。第一个选项有效,但耦合度很高(这意味着如果我想在组合中添加其他装饰器,我必须不断更改它(,而第二个版本耦合度较低,但仍然迫使我为每个服务装饰器编写一个工厂装饰器,从而导致更复杂的解决方案。

另一个痛点是对decorator本身的依赖性(例如,LoggingFooService上的ILogger<T>(,我可以通过利用ActivatorUtilities来创建decorator,而不是new手动对它们进行设置来解决这个问题。

我还可以潜在地将";工厂装饰工;因此,装饰函数是参数化的,因此类可以重用,但它仍然非常复杂,很难维护,同时也不能为消费者添加新的装饰器提供良好的语法。

class DecoratedFooServiceFactory<TDecorator> : FooServiceFactoryDecorator
where TDecorator : IFooService
{
private readonly IFooServiceFactory fooServiceFactory;
private readonly IServiceProvider serviceProvider;
public FooServiceFactory(
IFooServiceFactory fooServiceFactory, 
IServiceProvider serviceProvider)
{
this.fooServiceFactory = fooServiceFactory;
this.serviceProvider = serviceProvider;
}
public override IFooService CreateService(string heyItsNowAMethodLevelPeskyParameter)
{
return ActivatorUtilities.CreateInstance<TDecorator>(
this.serviceProvider,
this.fooServiceFactory.CreateService(heyItsNowAMethodLevelPeskyParameter));
}
}
...
services
.AddTransient<IFooServiceFactory, FooServiceFactory>()
.Decorate<IFooServiceFactory, DecoratedFooServiceFactory<LoggingFooService>>()

最后,如果我想放弃使用工厂,转而直接使用服务,这将导致一个重大的设置更改,然后我必须直接在容器中再次配置所有装饰器,而不是像通常那样只删除工厂注册。

我如何使用这样的工厂,同时仍然可以使用简单的Scrutor语法在容器级别配置装饰器?

好的,首先是几个免责声明:
  1. 我同意Steven的观点,因为这看起来像是一种反模式,您可能会更好地重新设计代码,使其在服务构建中不需要运行时值
  2. 另外,我想提醒大家不要使用像Decorate这样的scrator。虽然我对这一点的信心比第一点要低得多,但我相信从长远来看,在装饰器中隐藏日志并不像最初看起来那么方便。或者至少这是我在试用了大约一年后看到的
也就是说,让我们看看能做些什么

首先,让我们对值的来源进行一些限制。具体来说,假设我们可以有一个提供这种价值的服务,看起来像这样:

public interface IValueProvider
{
string Get();
}

这实际上让我们有了相当大的范围。该接口的实现可以:

  • 从外部API获取值-一次或定期在后台。它甚至可以在每次调用Get时都调用它,但这是一个非常糟糕的想法,因为它会使构造异步
  • 获取存储在内存中的值,并允许其他服务对其进行更新。例如,公开一个"配置"端点,用户可以在其中每隔一段时间设置一个新值
  • 根据您选择的某些算法计算值

一旦你有了这项服务,你就可以这样注册:

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IValueProvider, AwesomeValueProvider>();
services.AddSingleton<IFooServiceFactory, FooServiceFactory>(); 
services.AddTransient<IFooService>(sp => 
{
var factory = sp.GetRequiredService<IFooServiceFactory>();
var valueProvider = sp.GetRequiredService<IValueProvider>();
return factory.Create(valueProvider.Get());
});
}

希望这能帮助

最新更新