【问题标题】:Constructor injection (DI) vs. static factories for cross cutting concerns?构造函数注入(DI)与静态工厂的横切关注?
【发布时间】:2014-08-11 11:10:33
【问题描述】:

在大多数任意应用程序中,需要在所有可用层之间解决许多横切问题,例如日志记录、消息总线、配置。我注意到,在某些类中,如果使用 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();
}

【问题讨论】:

  • 没有直接关系,但很有趣:stackoverflow.com/questions/1985717/….
  • 阅读thisthis
  • @MarcelN。确实。这是 IMO 解决依赖/参数过多问题的正确方法。
  • @YuvalItzchakov 很好的例子,但是,正如你在第二个例子中看到的那样,作者将几个领域关注点捆绑到一个外观中,该外观执行特定的领域相关任务。我认为这对横切问题没有意义。我的意思是,在一个类中我需要一个记录器、一个异常处理程序和一个跟踪器,在另一个类中我需要一个记录器、一个跟踪器和一个事件总线。那么外墙会是什么样子呢? ICoreServicesWithLogger、ICoreServicesWithoutEventBus、ICoreServicesForExceptions?
  • 制作多个外观,并根据需要将适当的外观注入到类中。我要做的是将记录器、异常处理程序和跟踪器封装到一个外观服务中(只是因为它们的工作线看起来相似),然后将 that 外观注入另一个外观,其中还包括事件总线.

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


【解决方案1】:

将依赖项移出构造函数并不能解决问题,因为您不会降低类的依赖项数量,并且您仍然违反Single Responsibility principleOpen/Close principle 的可能性很大,导致您的代码难以测试、难以更改和难以维护。

通常一个好的解决方案是将这些横切关注点从您的组件中提取出来,并将它们放入专门为该横切关注点定制的组件中,并且该组件包装原始组件。换句话说:创建decorators

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

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

【讨论】:

    【解决方案2】:

    如果您更关注 TDD,您可以轻松猜出哪种方法更好。

    通过依赖注入,您的代码变得更加(单元)可测试。您可以通过一些模拟框架注入依赖项并轻松创建单元测试。

    但是在静态工厂的情况下,由于您的工厂类(硬)连接到您的类中,而在单元测试中,您无法从类外部注入它们。

    DI 相对于静态工厂的优势 -

    1. 并发开发 - 想想你正在使用的一个日志服务,它是由其他人构建的,你要对你的代码进行单元测试(而你并不关心单元测试日志服务,因为你假设它应该在你使用它时进行单元测试)。您使用 DI,使用模拟对象注入依赖项并完成。

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

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

    【讨论】:

    • +1。我要补充一点,您通常不希望(需要)模拟横切关注点。因此,在 OP 的示例中,事件总线可能是您不希望成为静态的东西。我会说即使你不做 TDD 也是如此,但从这些方面考虑它可以更容易地看出什么可以/应该被模拟(并因此被注入)和什么不应该。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    • 1970-01-01
    • 2017-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多