Ray*_*Ray 5 c# inversion-of-control unity-container simple-injector
我正在努力提高IoC容器的性能.我们使用的是Unity和SimpleInjector,我们有一个带有这个构造函数的类:
public AuditFacade(
IIocContainer container,
Func<IAuditManager> auditManagerFactory,
Func<ValidatorFactory> validatorCreatorFactory,
IUserContext userContext,
Func<ITenantManager> tenantManagerFactory,
Func<IMonitoringComponent> monitoringComponentFactory)
: base(container, auditManagerFactory, GlobalContext.CurrentTenant,
validatorCreatorFactory, userContext, tenantManagerFactory)
{
_monitoringComponent = new Lazy<IMonitoringComponent>(monitoringComponentFactory);
}
Run Code Online (Sandbox Code Playgroud)
我还有另一个使用此构造函数的类:
public AuditTenantComponent(Func<IAuditTenantRepository> auditTenantRepository)
{
_auditTenantRepository = new Lazy<IAuditTenantRepository>(auditTenantRepository);
}
Run Code Online (Sandbox Code Playgroud)
我看到第二个在大多数时间内在1毫秒内得到解决,而第一个平均需要50-60毫秒.我确定较慢的原因是因为参数,它有更多的参数.但是,如何才能提高速度较慢的性能呢?这是我们Func<T>用作参数的事实吗?如果它导致缓慢,我可以改变什么?
您当前的设计可能需要改进很多.这些改进可分为五个不同的类别,即:
普遍的共识是你应该更喜欢组合而不是继承.与使用组合相比,继承经常被过度使用并且通常会增加复杂性.通过继承,派生类与基类实现紧密耦合.我经常看到一个基类被用作实用的实用程序类,其中包含各种帮助方法,用于横切关注点和某些派生类可能需要的其他行为.
通常更好的方法是一起删除基类,并将服务注入到实现(AuditFacade在您的情况下为类)中的服务,该实现仅暴露服务所需的功能.或者在交叉问题的情况下,不要完全注入该行为,而是使用装饰器包装实现,该装饰器扩展了类的行为以及横切关注点.
在您的情况下,我认为复杂性正在发生,因为实现中没有使用7个注入的依赖项中的6个,而是仅传递给基类.换句话说,这6个依赖项是基类的实现细节,而实现仍然被迫了解它们.通过抽象(部分)服务后面的基类,可以最大限度地减少AuditFacade需要两个依赖项的依赖项数量:Func<IMonitoringComponent>新抽象.该抽象背后的实现将具有6个构造函数依赖项,但AuditFacade(和其他实现)对此无动于衷.
这AuditFacade取决于IIocContainer抽象,这非常类似于服务定位器模式的实现.服务定位器应被视为反模式,因为:
它隐藏了一个类的依赖项,导致运行时错误而不是编译时错误,以及使代码更难维护,因为它不清楚何时引入一个重大变化.
总是有更好的替代方法将容器或抽象注入容器中的应用程序代码.请注意,有时您可能希望将容器注入工厂实现,但只要将它们放在组合根中,就没有任何害处,因为Service Locator是关于角色而不是机制.
static GlobalContext.CurrentTenant属性是Ambient Context反模式的实现.Mark Seemann和我在我们的书中写到了这种模式:
AMBIENT CONTEXT的问题与SERVICE LOCATOR的问题有关.主要问题是:
- 依赖性是隐藏的.
- 测试变得更加困难.
- 根据其上下文更改DEPENDENCY变得非常困难.[第5.3.3段]
在这种情况下使用是非常奇怪的IMO,因为您从构造函数内部的某些静态属性中获取当前租户以将其传递给基类.为什么基类不调用该属性本身?
但没有人应该称之为静态属性.使用这些静态属性会使您的代码更难以阅读和维护.它使得单元测试更加困难,并且由于你的代码库通常会被调用这样的静态,它变成了隐藏的依赖; 它与使用服务定位器具有相同的缺点.
一漏抽象是依赖倒置原则冲突,其中违反了抽象的原则,即第二部分:
B.抽象不应该依赖于细节.细节应取决于抽象.
虽然Lazy<T>它本身不是抽象(Lazy<T>是一种具体类型),但当用作构造函数参数时,它可能变成漏洞抽象.例如,如果您正在注入Lazy<IMonitoringComponent>而不是IMonitoringComponent直接注入(这是您在代码中基本上执行的操作),则新的Lazy<IMonitoringComponent>依赖项会泄漏实现细节.这Lazy<IMonitoringComponent>向消费者传达所使用的IMonitoringComponent实现是昂贵的或耗时的创建.但为什么消费者会关心这个呢?
但是这有更多的问题.如果在某个时间点使用的IUserContext实现变得昂贵,我们必须开始在整个应用程序中进行彻底的更改(违反开放/封闭原则),因为所有IUserContext依赖关系都需要更改为,Lazy<IUserContext>并且IUserContext必须更改所有依赖关系改为使用userContext.Value..而且你还必须改变所有的单元测试.如果您忘记更改一个IUserContext引用Lazy<IUserContext>或IUserContext在您创建新类时意外依赖时会发生什么?您的代码中存在错误,因为此时会立即创建用户上下文实现,这会导致性能问题(这会导致问题,因为这是您Lazy<T>首先使用的原因).
那么,为什么我们正在对我们的代码库进行彻底的更改并使用额外的间接层来污染它?没有理由这样做.依赖性创建成本高昂的事实是一个实现细节.你应该把它隐藏在抽象之后.这是一个例子:
public class LazyMonitoringComponentProxy : IMonitoringComponent {
private Lazy<IMonitoringComponent> component;
public LazyMonitoringComponentProxy(Lazy<IMonitoringComponent> component) {
this.component = component;
}
void IMonitoringComponent.MonitoringMethod(string someVar) {
this.component.Value.MonitoringMethod(someVar);
}
}
Run Code Online (Sandbox Code Playgroud)
在这个例子中,我们隐藏了Lazy<IMonitoringComponent>一个代理类.这允许我们IMonitoringComponent用这个替换原始实现,LazyMonitoringComponentProxy而不必对应用程序的其余部分进行任何更改.使用Simple Injector,我们可以注册以下类型:
container.Register<IMonitoringComponent>(() => new LazyMonitoringComponentProxy(
new Lazy<IMonitoringComponent>(container.GetInstance<CostlyMonitoringComp>));
Run Code Online (Sandbox Code Playgroud)
就像Lazy<T>可以被滥用作为漏洞抽象一样,同样适用Func<T>,特别是当你出于性能原因这样做时.正确应用DI时,大多数情况下无需将工厂抽象注入代码中Func<T>.
请注意,如果您正在注射Lazy<T>并且Func<T>遍布整个地方,则会使您不必要的代码库变得复杂.
但是,除了Lazy<T>和Func<T>被泄漏的抽象,你需要他们很多事实是你的应用程序中的问题的指示,因为注射的构造要简单.如果构造函数需要很长时间才能运行,那么构造函数就会做得太多.构造函数逻辑通常很难测试,如果这样的构造函数调用数据库或从HttpContext请求数据,那么验证对象图会变得更加困难,以至于您可能会一起跳过验证.跳过对象图的验证是一件非常糟糕的事情,因为这会强制您单击整个应用程序以查明您的DI容器是否配置正确.
我希望这能为您提供有关改进课程设计的一些想法.