【问题标题】:What if Dependency Injection is not possible?如果无法进行依赖注入怎么办?
【发布时间】:2011-06-30 22:16:48
【问题描述】:

经过多次踢腿和尖叫后,我开始接受 DI,尽管随着依赖项的增长,SL 看起来会变得更加清晰。

但是,IMO 在 DI 方面仍然存在重大问题:

当您无法控制对象的实例化时,DI 是不可能的。在 ASP.NET 世界中,示例包括:HttpModule、HttpHandler、Page 等。

在上述场景中,我们将求助于静态服务位置来解决依赖关系,通常通过HttpContext.Current,它总是从当前线程推断范围。因此,如果我们要在这里使用静态 SL,那么为什么不在其他地方也使用它呢?

答案是否很简单:咬紧牙关,必要时使用 SL(如上),但尝试支持 DI?如果是这样:是否只使用一次静态 SL 可能会破坏整个应用程序的一致性?从本质上消除 DI 在其他地方的辛勤工作?

【问题讨论】:

  • 为什么会有静态 SL?
  • @Phill:接口是静态的,尽管返回的实际实例可能仅限于特定上下文。例如: HttpContext.Current 是一个静态方法,虽然反映它会表明它实际上推断从当前线程返回哪个 HttpContext 实例。然而,静态是静态的,不管底层工厂逻辑有多“聪明”;它仍然存在根本缺陷,因为必须将单个工厂存储在全局/应用程序状态中。
  • 无论如何,HttpContext 不应该是你的 SL 的一部分,HttpContext 是框架的一部分,我不明白 HttpContext.Current 是静态的对你的 SL 是否是静态的有什么影响。
  • 相关性在于它是 only 可用于 HttpModules(和其他)的对象,可用于推断当前范围。我们不能注入模块,因为它们是由 ASP.NET 运行时实例化的;因此,要引用由另一个组件创建的对象,我们将不得不使用类似 HttpContext.Current.Items["MyDependency"] 的东西,或者更糟糕的是,使用静态变量 MyDependency.Instance。
  • 开始写单元测试你很快就会发现SL很痛苦。

标签: c# asp.net dependency-injection dependency-management service-locator


【解决方案1】:

在这些情况下,典型的方法是编写一个通用的基础设施级包装器/适配器,它确实使用服务定位器,但为使用此适配器的应用程序级类提供干净的 IoC。

我这样做是为了在 HttpModulesProviders 中启用 IoC,这是框架不允许控制实例化的两种情况。类似的方法可以用于其他类似的实体。

这里的重点是,此类包装器/适配器代码是您的可重用基础架构的一部分,而不是您的应用程序级代码。通常不建议在您的应用程序级代码中使用服务位置。

某些容器提供的“BuildUp”功能无法解决这些限制。 BuildUp 不允许您重新获得对实例化的控制权,因此您将无法使用某些容器功能,例如代理。

【讨论】:

    【解决方案2】:

    一个好的代码库也是一个对最佳解决方案的偶尔限制包含在小范围内的代码库。缺乏 DI 的可能性部分解释了从 ASP.NET 到 MVC 对应物的转变。对于 HttpHandlers,我通常有一个 HttpHandler Factory,它是唯一一个弄脏它的“手”并从容器中实例化 HttpHandlers 的类。

    许多 DI 容器都有某种 BuildUp 方法来对已实例化的对象执行 setter 注入。如果您有服务位置,请尝试将其隔离到您的应用程序的某个部分,这纯粹是一个基础设施问题。然后它将作为环境限制的阻尼场。

    简而言之,一些 SL 不会使您的努力无效,但请确保 SL 位是基础设施且包含良好的。

    【讨论】:

      【解决方案3】:

      以示例提供@flq 的答案。

      我正在开发一个相当广泛地使用 Ninject 的 ASP.Net MVC 项目。有一些地方我要么不能使用 DI(扩展方法),要么不使用它更干净,而是使用 SL(我所有控制器的基类,构造函数注入会使控制器的所有构造函数变得混乱)。

      所以,作为妥协,我使用了一个轻微的混合:

      public interface IServiceLocator
      {
          T Create<T>();
      }
      
      public class NinjectServiceLocator : IServiceLocator
      {
          private readonly IKernel kernel;
      
          public NinjectServiceLocator(IKernel kernel)
          {
              this.kernel = kernel;
          }
      
          public T Create<T>()
          {
              return kernel.Get<T>();
          }
      }
      
      public class ServiceLocator
      {
          private static IServiceLocator current;
      
          public static IServiceLocator Current
          {
              get
              {
                  return current;
              }
      
              set
              {
                  current = value;
              }
          }
      
          public T Create<T>()
          {
              return current.Create<T>();
          }
      }
      

      这可能不是最好的选择,也不是严格的 SL 模式,但它对我有用。额外的接口让我可以换掉定位器本身进行测试。 IKernel 是 Ninject 配置的主要 DI 对象,因此请用您自己的框架替换,因为我遇到的大多数都有类似的核心。

      【讨论】:

        【解决方案4】:

        有时您无法避免紧密耦合,就像在您的示例中一样。但是,这并不意味着您也需要接受它。相反,通过封装混乱并将其从您的日常生活中分离出来来隔离它。

        例如,如果我们想要当前的 Forms 身份验证用户,我们别无选择,只能访问 HttpContext.Current.Request.User.Identity.Name。但是,我们可以选择在哪里拨打电话。

        HttpContext.Current 是一个问题的解决方案。当我们直接从我们使用结果的地方调用它时,我们在同一个地方声明了问题和解决方案:“我需要当前用户名,它被坚定地声明为来自当前 HTTP 上下文。”这混淆了两者的定义,并且不允许对同一问题有不同的解决方案。

        我们缺少的是对我们正在解决的问题的清晰表述。对于这个例子,它会是这样的:

        确定发出当前请求的用户

        我们使用HttpContext.Current,甚至是用户名这一事实并不是核心问题定义的一部分;这是一个实现细节,只会使需要当前用户的代码复杂化。

        我们可以通过接口表示检索当前用户的意图,无需实现细节:

        public interface IUserContext
        {
            User GetUser();
        }
        

        我们直接调用HttpContext.Current 的任何类现在都可以改用这个由容器注入的接口来维护 DI 的优点。对于一个类来说,在其构造函数中接受 IUserContext 也比在其公共 API 中无法看到依赖项更具意图。

        实现将静态调用隐藏在一个不会再损害我们的对象的地方:

        public class FormsUserContext : IUserContext
        {
            private readonly IUserRepository _userRepository;
        
            public FormsUserContext(IUserRepository userRepository)
            {
                _userRepository = userRepository;
            }
        
            public User GetUser()
            {
                return _userRepository.GetByUserName(HttpContext.Current.Request.User.Identity.Name);
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-09-18
          • 2011-08-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-11
          • 2019-10-18
          相关资源
          最近更新 更多