在大多数任意应用程序中,有许多横切关注点需要在所有可用层中解决,例如日志记录、消息总线、配置。我注意到的是,在一些类中,如果使用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();
}
将依赖项移出构造函数并不能解决问题,因为你没有降低类的依赖项数量,而且很有可能你仍然违反了单一职责原则和打开/关闭原则,导致你的代码难以测试,难以更改和难以维护。
相反,通常一个好的解决方案是将这些横切关注点从您的组件中拉出来,并将它们放入专门为该横切关注点定制的组件中,并且该组件包装了原始组件。换句话说:创建装饰器。
这可能会迫使你改变你的类的设计,因为当你没有通用的抽象来定义相关的服务集时,你将不得不为每个抽象定义一个装饰器,这将导致大量的代码重复,这在几乎所有情况下都是不好的。
因此,围绕命令/处理程序和查询/处理程序建模您的系统,您将处于更好的位置。您可以使用通用的装饰器来装饰每个业务逻辑片段,您只需定义一次,就可以在任何地方重用它。
如果您更熟悉TDD,您可以很容易地猜出哪种方法更好。
通过依赖注入,您的代码变得更加(单元)可测试。你可以通过一些mock框架注入依赖并创建你的单元测试,而不会有太多的麻烦。但是在静态工厂的情况下,由于您的工厂类(硬)连接到您的类中,而单元测试没有办法从类外部注入它们。
DI相对于静态工厂的优势-
Concurrent Development -考虑一个你正在使用的日志服务,它是由别人构建的,你要对你的代码进行单元测试(你不关心日志服务的单元测试,因为你假设,当你使用它时,它应该进行单元测试)。你最好使用DI,使用模拟对象注入依赖项,完成。
速度 -当单元测试你的类时,你肯定不希望它们花很长时间(这样你就可以在每次修改主类时休息一下;))。您肯定希望单元测试能够在眨眼之间运行并报告任何错误。依赖于外部资源(例如网络/数据库,文件系统)的静态工厂将花费时间。您最好使用DI,使用模拟对象,然后完成。
teststability - DI帮助将客户端从依赖中隔离出来(促进接口的使用),因此提高了可测试性(通过使用mock)。