简体   繁体   中英

What if Dependency Injection is not possible?

After much kicking and screaming, I'm starting to accept DI despite how much cleaner SL may seem as dependencies grow.

However, IMO there's still a significant show-stopper with regards to DI:

DI is not possible when you don't have control over an object's instantiation. In the ASP.NET world, examples include: HttpModule, HttpHandler, Page, etc.

In the above scenario we would resort to static service location to resolve dependencies, typically via HttpContext.Current , which invariably infers scope from the current thread. So if we're going to use static SL here, then why not use it else where too?

Is the answer as simple as: grit your teeth and use SL when necessary (like above), but try and favor DI? And if so: doesn't using static SL just once potentially break the consistency of an entire application? Essentially undoing the hard work of DI everywhere else?

Sometimes you just can't avoid tight coupling, like in your examples. However, that doesn't mean you need to embrace it either. Instead, quarantine it by encapsulating the messiness and factoring it away from your day-to-day life.

For example, if we want the current Forms Authentication user, we don't have much choice but to access HttpContext.Current.Request.User.Identity.Name . We do, however, have the choice of where to make that call.

HttpContext.Current is a solution to a problem. When we call it directly from where we use the results, we are declaring the problem and solution in the same place: "I need the current user name which is declared unwaveringly as coming from the current HTTP context." This muddles the definition of both and doesn't allow for different solutions to the same problem.

What we are missing is a clear articulation of the problem we are solving. For this example, it would be something like:

Determine the user which made the current request

The fact that we are using HttpContext.Current , or even a user name, is not a part of the core problem definition; it is an implementation detail that only serves to complicate the code requiring the current user.

We can represent the intent to retrieve the current user, sans implementation details, through an interface:

public interface IUserContext
{
    User GetUser();
}

Any class where we called HttpContext.Current directly can now use this interface instead, injected by the container, to maintain DI goodness. It is also much more intention-revealing for a class to accept an IUserContext in its constructor than to have a dependency which can't be seen from its public API.

The implementation buries the static call in a single place where it can't harm our objects any more:

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);
    }
}

A good codebase is also one where the occasional restriction on the optimal solution is contained to a small area. The lack of possibility of DI partly explains the move from ASP.NET to the MVC counterpart. With HttpHandlers, I usually have a HttpHandler Factory which is the only class to get its "hands" dirty and instantiates HttpHandlers from a container.

Many DI containers have some kind of BuildUp method to perform setter injection on already instantiated objects. If you have service location, try to isolate it into some piece of your application that is purely an infrastructure concern. It will then act as a dampening field to the environment's limitations.

In short, some SL will not invalidate your efforts, but make sure the SL bits are infrastructural and well-contained.

To provide on @flq's answer with an example.

I'm working on an ASP.Net MVC project that makes rather extensive use of Ninject. There were a handful of places where I either couldn't use DI (Extension methods), or it was cleaner not to, and use SL instead (The base class for all of my controllers, constructor injection would make all the constructors for controllers messy).

So, as a compromise, I use a slight hybrid:

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>();
    }
}

It may not be the best option, or a strict SL pattern, but it works for me. The additional interface lets me swap out the locator itself for testing. IKernel is Ninject's main DI object that gets configured, so substitute with your own framework, as most I've encountered have a similar core.

The typical approach in these cases is to write a generic infrastructure-level wrapper/adapter that does use Service Locator, but provides clean IoC for application-level classes that use this adapter.

I did this to enable IoC in HttpModules and Providers , two cases where the framework doesn't allow control of instantiation. A similar approach can be used for other similar entities.

The point here is that such wrapper/adapter code is part of your reusable infrastructure , and not your application-level code. What's generally not recommended is using service location in your application-level code .

The "BuildUp" feature that some containers offer are a poor workaround for these limitations. BuildUp doesn't allow you to regain control of instantiation, so you won't be able to use some container features such as proxying.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM