在大多数任意应用中,需要在所有可用层之间解决许多交叉问题,例如:记录,消息总线,配置。我注意到的是,在某些类中,如果使用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();
}
答案 0 :(得分:6)
如果你更喜欢TDD,你可以很容易地猜出哪种方法更好。
通过依赖注入,您的代码变得更加(单元)可测试。您可以通过一些模拟框架注入依赖项,并创建单元测试而不会有太多麻烦。
但是对于静态工厂,由于您的工厂类(硬)连接到您的班级,而单元测试,您无法从课外注入它们。
DI对静态工厂的好处 -
并发开发 - 想想您正在使用的日志服务,这是由其他人构建的,您将对您的代码进行单元测试(而且您不会由于您假设,日志服务的护理单元测试,它应该在您使用它时进行单元测试)。你打击使用DI,使用模拟对象注入依赖并完成。
速度 - 在对您的课程进行单元测试时,您肯定不会希望它们花费很长时间(因此,它会让您在主要课程的每次更改时都能享用咖啡时间。 class;))。您肯定希望单元测试在眨眼间运行并报告任何错误。依赖于外部资源(例如网络/数据库,文件系统)的静态工厂需要时间。你最好使用DI,使用模拟对象并完成。
可测试性 - DI有助于将客户端与其依赖关系隔离开来(促进接口的使用),从而提高可测试性(通过使用模拟)。
答案 1 :(得分:6)
将依赖项移出构造函数并不能解决问题,因为你不会降低一个类的依赖关系数量,而且你仍然违反了Single Responsibility principle和Open/Close principle,导致您的代码难以测试,难以更改且难以维护。
相反,通常一个好的解决方案是将这些横切关注问题从您的组件中拉出来,并将它们放入专为该交叉问题量身定制的组件中,并将该组件包裹在原始组件中。换句话说:创建decorators。
这可能会强迫您更改类的设计,因为当您没有通用抽象来定义相关服务集时,您将不得不为每个抽象定义一个装饰器,这将导致很多代码重复,几乎在所有情况下都很糟糕。
相反,请围绕command/handlers和query/handlers对您的系统进行建模,您将会处于更好的位置。您可以使用您定义一次的通用装饰器来装饰每个业务逻辑,并重复使用所有位置。这可以保持系统清洁,但仍然非常灵活。