构造函数中的Func <T>参数是否会减慢我的IoC解析速度? - c#

我正在尝试提高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);
}

我也有这个构造函数的另一个类:

public AuditTenantComponent(Func<IAuditTenantRepository> auditTenantRepository)
{
    _auditTenantRepository = new Lazy<IAuditTenantRepository>(auditTenantRepository);
}

我看到大多数情况下,第二个问题在1毫秒内得到解决,而第一个问题平均需要50-60毫秒。我敢肯定,速度较慢的原因是由于参数,它具有更多的参数。但是,如何改善这种速度较慢的服务器的性能呢?我们使用Func<T>作为参数是事实吗?如果它导致速度缓慢,该怎么办?

参考方案

您当前的设计可能还有很多改进之处。这些改进可以分为五个不同的类别,即:

可能滥用基类
使用服务定位器反模式
使用环境上下文反模式
泄漏的抽象
在注入构造器中做得太多

可能滥用基类

普遍的共识是您应该选择composition over inheritance。与使用组合相比,继承经常被过度使用,并且通常会增加更多的复杂性。通过继承,派生类与基类实现紧密耦合。我经常看到一个基类被用作实用工具类,其中包含各种辅助方法,这些方法可以解决某些派生类可能需要的横切关注点和其他行为。

通常更好的方法是一起删除所有基类,然后将服务注入到只暴露服务所需功能的实现中(在您的情况下为AuditFacade类)。或者,如果涉及到横切关注点,则根本不要注入该行为,而应使用decorator包装实现,以扩展具有横切关注点的类的行为。

在您的情况下,我认为复杂性显然正在发生,因为实现中未使用7个注入的依赖中的6个,而是仅将其传递给基类。换句话说,这6个依赖项是基类的实现细节,而实现仍然被迫了解它们。通过在服务之后抽象该基类(的一部分),可以将AuditFacade需要的依赖关系数量减少到两个依赖关系:Func<IMonitoringComponent>和新抽象。该抽象背后的实现将有6个构造函数依赖项,但是AuditFacade(和其他实现)对此没有注意。

使用服务定位器反模式

AuditFacade依赖于IIocContainer抽象,这非常类似于Service Locator pattern的实现。 Service Locator should be considered an anti-pattern因为:

它隐藏了一个类的依赖关系,从而导致运行时错误而不是
编译时错误,以及使代码更难处理
维护,因为不清楚何时会引入
突破性的变化。

总有更好的选择,可以将容器或容器上的抽象注入到应用程序代码中。请注意,有时您可能希望将容器注入工厂实现中,但是只要将它们放置在Composition Root内,就不会有任何危害,因为Service Locator is about roles, not mechanics。

使用环境上下文反模式

静态GlobalContext.CurrentTenant属性是Ambient Context反模式的实现。 Mark Seemann,我在our book中写到这种模式:

“环境上下文”的问题与“服务”的问题有关
定位器。主要问题是:

DEPENDENCY隐藏。
测试变得更加困难。
根据其上下文更改DEPENDENCY变得非常困难。 [第5.3.3段]

这种情况下的使用确实是很奇怪的IMO,因为您是从构造函数内部的某个静态属性中获取当前租户的,并将其传递给基类。为什么基类本身不调用该属性?

但是没有人应该称其为静态属性。这些静态属性的使用使您的代码难以阅读和维护。它使单元测试更加困难,并且由于您的代码库通常会被此类静态调用所困扰,因此它成为隐藏的依赖项;它具有与使用Service Locator相同的缺点。

泄漏的抽象

Leaky Abstraction是Dependency Inversion Principle违反,其中抽象违反了原则的第二部分,即:

B.抽象不应依赖细节。细节应取决于
抽象。

尽管Lazy<T>本身不是抽象(Lazy<T>是具体类型),但是当用作构造函数参数时,它可能会成为泄漏抽象。例如,如果您直接注入Lazy<IMonitoringComponent>而不是IMonitoringComponent(这基本上是您在代码中所做的事情),则新的Lazy<IMonitoringComponent>依赖项会泄漏实现细节。此Lazy<IMonitoringComponent>向消费者传达所使用的IMonitoringComponent实现创建起来昂贵或耗时。但是消费者为什么要关心这个呢?

但是与此有关的还有更多问题。如果在某个时间点使用的IUserContext实现的创建成本很高,则必须开始在整个应用程序中进行全面更改(违反Open/Closed Principle),因为所有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);
    }
}

在此示例中,我们将Lazy<IMonitoringComponent>隐藏在proxy class后面。这使我们可以用此IMonitoringComponent替换原始的LazyMonitoringComponentProxy实现,而不必对其余应用程序进行任何更改。使用Simple Injector,我们可以按以下方式注册此类型:

container.Register<IMonitoringComponent>(() => new LazyMonitoringComponentProxy(
    new Lazy<IMonitoringComponent>(container.GetInstance<CostlyMonitoringComp>));

正如Lazy<T>可能被用作泄漏抽象一样,Func<T>也是如此,尤其是出于性能原因而这样做时。正确应用DI时,大多数时候不需要将工厂抽象注入代码中,例如Func<T>

请注意,如果要在各处注入Lazy<T>Func<T>,则会使代码库变得不必要。

在注入构造器中做得太多

但是,除了Lazy<T>Func<T>是泄漏抽象之外,您实际上非常需要它们的事实表明您的应用程序存在问题,因为Injection Constructors should be simple。如果构造函数需要很长时间才能运行,则您的构造函数会做太多事情。构造器逻辑通常很难测试,并且如果这样的构造器调用数据库或从HttpContext请求数据,则verification of your object graphs变得更加困难,以至于您可能一起跳过验证。跳过对对象图的验证是一件很糟糕的事情,因为这迫使您单击整个应用程序以了解您的DI容器是否配置正确。

我希望这能给您一些有关改进班级设计的想法。

将谓词<T>转换为Func <T,bool> - c#

我有一个包含成员Predicate的类,希望在Linq表达式中使用该类:using System.Linq; class MyClass { public bool DoAllHaveSomeProperty() { return m_instrumentList.All(m_filterExpression); } private IEnumerable&…

合并List <T>和List <Optional <T >> - java

鉴于: List<Integer> integers = new ArrayList<>(Arrays.asList( 10, 12 )); List<Optional<Integer>> optionalIntegers = Arrays.asList( Optional.of(5), Optional.em…

无法从ArrayList <String>转换为List <Comparable> - java

当我写下面的代码时,编译器说 无法从ArrayList<String>转换为List<Comparable>private List<Comparable> get(){ return new ArrayList<String>(); } 但是当我用通配符编写返回类型时,代码会编译。private List&l…

OpenShift构建错误:无法在多模块Maven Spring启动项目的父模块中导入子模块类 - java

我有一个使用spring的多模块Maven项目。通用模块类用作业务模块项目中的直接导入。我可以在本地PC上编译并成功运行它们。当我在OpenShift中部署相同的模块时,出现错误,无法在业务模块中导入通用模块类。项目结构可以总结如下:项目根 通用模块 src pom.xml 业务模块 src pom.xml pom.xml父POM:<?xml vers…

List <Dog>是List <Animal>的子类吗?为什么Java泛型不是隐式多态的? - java

我对Java泛型如何处理继承/多态感到困惑。假设以下层次结构-动物(父母)狗-猫(儿童)因此,假设我有一个方法doSomething(List<Animal> animals)。根据继承和多态性的所有规则,我假设List<Dog>是List<Animal>,而List<Cat>是List<Animal&g…