次の方法で共有


Inject or locate dependencies?

Inversion of Control pattern allows to decouple components (consumers) from their dependencies and takes care of dependencies location and lifetime management through delegation of these responsibilities to external (with respect to dependent type) component. This pattern actively used in composite application development.

Inversion of Control comes in two flavors (Unity provides both capabilities):

Service locator holds references to services and knows how to locate them. It is further used by dependent component to obtain necessary services. In other words consumers play active role.

 interface IService
{
    void Do();
}

class ActiveConsumer
{
    private readonly IUnityContainer m_locator;

    // Reference to service locator comes from outside
    public ActiveConsumer(IUnityContainer serviceLocator)
    {
        m_locator = serviceLocator;
    }

    public void Do()
    {
        // In order to fulfill its task active consumer relies 
        // on service implementation that is obtained on demand 
        // from service locator
        var service = m_locator.Resolve<IService>();
        service.Do();
    }
}

Dependency injection makes dependent components passive (little or no work is done to get its dependencies). The only responsibility consumers still care about is to express their dependencies somehow (the way dependencies are expressed depends on pattern implementation, but for this example we will use single constructor automatic injection supported by Unity).

 class PassiveConsumer
{
    private readonly IService m_service;

    // This constructor is used to inject service dependency
    public PassiveConsumer(IService svc)
    {
        m_service = svc;
    }

    public void Do()
    {
        // We got this dependency from outside and done nothing 
        // to let it happen - so just use it
        m_service.Do();
    }
}

...

// At this point container resolves consumer's dependency 
// and injects it during construction 
var passiveConsumer = container.Resolve<PassiveConsumer>();
passiveConsumer.Do();

So what is the difference?

First, is dependency from service locator appropriate? If the component in question is supposed to be reused by others you may end up with putting unnecessary constraints (for example you are using some open source service locator but developers that could reuse your component are not allowed to use any open source stuff due to customer’s demand and thus won’t be able to reuse the component).

Second, dependencies visibility. Service locator makes consumer’s “real” dependencies hidden and dependency from service locator itself visible. When dependencies are explicit it is much easier to understand dependent class. Explicit dependencies allows you to assess and control the growth of the component. For example, if your component accepts 10 services in its constructor it may be a sign that it does, or knows or decides too much and it is time to split it. Consider the same thing when using service locator. In order for you to spot number of dependencies you need to look for all unique usage occurrences of service locator. It is not that hard with modern IDE but still it is not that easy as looking at component’s contract.

On the other hand, it makes sense to consider the audience of the component. If it will be reused by others and dependencies are hidden it may require deep knowledge of component’s inner workings in order to use it.

Third, consumer’s relation with dependency. Dependency injection promotes constant relations (from lifetime perspective) . Consumer obtains its dependency at construction time and lives with it. On the other hand service locator compasses to temporary relations – get service instance when it is time, call its methods, discard it. Why discard? Because if the component has a constant relation why not use dependency injection otherwise which gives you explicit dependencies?

But anyway, what particular case forces locator usage? When consumer has longer lifetime than its dependency. For example, you are writing smart client application. You organized presentation layer using Model-View-Presenter pattern. Presenter calls remote service in response to user interaction. View controlled by a presenter can be opened for a long time. If presenter gets remote service proxy dependency only once it may happen that proxy will go into faulted state (for example, due to network connectivity problems) and any subsequent calls to it will result in exception. So it is better to dispose proxy every time a particular task accomplished and create new one when new task is on the way or cache it and in response to proxy going faulted create a new one (which is of course harder as long as you need to handle all cases where it used and maintain cache). Thus it seems that service locator is more appropriate in this case.

However we can make short lived dependencies explicit. Let’s assume that IService implementation instance must be disposed every time it is used.

 interface IService : IDisposable
{
    void Do();
}

// This is still active consumer as it uses service locator to get service instance
class ActiveConsumer
{
    private readonly IUnityContainer m_locator;

    public ActiveConsumer(IUnityContainer serviceLocator)
    {
        m_locator = serviceLocator;
    }

    public void Do()
    {
        using (var service = m_locator.Resolve<IService>())
        {
            service.Do();
        }
    }
}

Service locator has wide surface (it terms of services it can provide) and this makes consumer’s contract opaque. What we need to do is narrow the surface but still provide ability to create service instances (as long as we need to dispose them every time). Abstract factory will do the thing. Factory provides clear connection with service it creates. On the other hand we need to make consumer’s dependency from factory explicit. We will use dependency injection.

 interface IServiceFactory
{
    IService CreateService();
}

// Consumer is no longer active as it gets its dependencies from outside
class PassiveConsumer
{
    private readonly IServiceFactory m_factory;

    // The dependency is now explicit
    public PassiveConsumer(IServiceFactory serviceFactory)
    {
        m_factory = serviceFactory;
    }

    public void Do()
    {
        // We still can create service instances on demand
        using (var service = m_factory.CreateService())
        {
            service.Do();
        }
    }
}

How about this? That is not all. Factory clearly and explicitly states the relation between dependent component and dependency (service that is created by factory) – it is a temporary relation (as long as it provides ability to create new instances).

Summary:

  • DO make dependencies explicit
  • DO use dependencies to assess and control growth of the dependent component
  • CONSIDER component audience when choosing between dependency injection and service locator
  • CONSIDER using abstract factory pattern and dependency injection to make short lived dependencies (in comparison with dependent component lifetime) explicit