6

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

然而,IMO 在 DI 方面仍有一个重要的亮点:

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

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

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

4

4 回答 4

16

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

例如,如果我们想要当前的 Forms Authentication 用户,我们别无选择,只能访问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);
    }
}
于 2011-02-20T23:43:21.727 回答
4

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

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

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

于 2011-02-20T22:08:26.523 回答
2

以示例提供@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 对象,因此请用您自己的框架替换,因为我遇到的大多数都有类似的核心。

于 2011-02-20T23:56:29.827 回答
2

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

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

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

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

于 2011-02-21T01:03:24.283 回答