构造函数注入(DI)与静态工厂的交叉关注问题?

时间:2014-08-11 11:10:33

标签: c# design-patterns dependency-injection factory cross-cutting-concerns

在大多数任意应用中,需要在所有可用层之间解决许多交叉问题,例如:记录,消息总线,配置。我注意到的是,在某些类中,如果使用IoC注入模块,它们往往会完全炸毁构造函数。

public class MyService : IService 
{
    public MyService(ILogger logger, IAppSettings settings, IEventBus eventBus...)
    {

    }
}

对于构造函数过度注入的常见情况,我倾向于将关注点折射成紧密组合在一起的构建块,因此我在类中获得的依赖项更少。但是,对于交叉切割概念,这是不可能的。

在日志框架中,静态工厂/服务似乎非常流行,例如

// Application root
MyLoggerService.SetFactory(log4NetFactory);

// Somewhere
MyLoggerService.GetLogger("name") // returns Log4NetLogger created by Log4NetFactory.

我的问题是:对于各种交叉切割的东西,这种方法是否很好?如果代码最终看起来像这样有什么缺点:

public class MyService : IService
{

    private readonly IReallyNeedThat _dependency;

    public MyService(IReallyNeedThat dependency)
    {
        _dependency = dependency;
    }

    private readonly ILogger _logger = LoggerService.GetLogger("MyService");
    private readonly IEventBus _eventBus = EventBusService.GetEventBus();
    private readonly IConfiguration _configuration = ConfigurationService.GetConfiguration(Level.Roaming)
    private readonly IExceptionHandler _exceptionHandler = ExceptionPolicy.GetHandler();
    private readonly ITracer _tracer = TraceManager.GetDebugTracer();
}

2 个答案:

答案 0 :(得分:6)

如果你更喜欢TDD,你可以很容易地猜出哪种方法更好。

通过依赖注入,您的代码变得更加(单元)可测试。您可以通过一些模拟框架注入依赖项,并创建单元测试而不会有太多麻烦。

但是对于静态工厂,由于您的工厂类(硬)连接到您的班级,而单元测试,您无法从课外注入它们。

DI对静态工厂的好处 -

  1. 并发开发 - 想想您正在使用的日志服务,这是由其他人构建的,您将对您的代码进行单元测试(而且您不会由于您假设,日志服务的护理单元测试,它应该在您使用它时进行单元测试)。你打击使用DI,使用模拟对象注入依赖并完成。

  2. 速度 - 在对您的课程进行单元测试时,您肯定不会希望它们花费很长时间(因此,它会让您在主要课程的每次更改时都能享用咖啡时间。 class;))。您肯定希望单元测试在眨眼间运行并报告任何错误。依赖于外部资源(例如网络/数据库,文件系统)的静态工厂需要时间。你最好使用DI,使用模拟对象并完成。

  3. 可测试性 - DI有助于将客户端与其依赖关系隔离开来(促进接口的使用),从而提高可测试性(通过使用模拟)。

答案 1 :(得分:6)

将依赖项移出构造函数并不能解决问题,因为你不会降低一个类的依赖关系数量,而且你仍然违反了Single Responsibility principleOpen/Close principle,导致您的代码难以测试,难以更改且难以维护。

相反,通常一个好的解决方案是将这些横切关注问题从您的组件中拉出来,并将它们放入专为该交叉问题量身定制的组件中,并将该组件包裹在原始组件中。换句话说:创建decorators

这可能会强迫您更改类的设计,因为当您没有通用抽象来定义相关服务集时,您将不得不为每个抽象定义一个装饰器,这将导致很多代码重复,几乎在所有情况下都很糟糕。

相反,请围绕command/handlersquery/handlers对您的系统进行建模,您将会处于更好的位置。您可以使用您定义一次的通用装饰器来装饰每个业务逻辑,并重复使用所有位置。这可以保持系统清洁,但仍然非常灵活。