views:

168

answers:

3

Can someone describe pros and cons of both methods, and when you prefer one over another?

interface IService
{
    object GetSomething();
}

class Service:IService{...}

DI

Container.Register<IService, Service>();

class A
{
    public A(IService service)
    {
        _service = service;
    }
}

Global

class Config
{
    public static IService Service {get;set;}
}

Config.Service = new Service();

class A
{
    public A()
    {
        _service = Config.Service;
    }
}
+6  A: 

Your 'Global' approach is more or less a Poor Man's Service Locator. Many people, including me, consider Service Locator an anti-pattern.

The main reason for that is that its usage is implicit; it is hidden from the consumer, so a developer writing client code will probably not be aware that it exists.

As a client developer, I could easily write this code:

var a = new A();

and then just start partying on a straight away. If A depends on IService, methods invoked on the instance are likely to throw exceptions if Config.Service was never set, and there is no guarantee that this ever happened.

On the other hand, when you use Constructor Injection, you can more or less guarantee that the IService instance is being injected. Now this code will not compile:

var a = new A();

so it forces client developers to supply an instance of IService. Obviously client developers could simply supply null:

var a = new A(null);

but you could easily fix this with a Guard Clause.

class A
{
    public A(IService service)
    {
        if(service == null)
        {
            throw new ArgumentNullException();
        }

        _service = service;
    }
}

This guarantees that the service instance is valid, particularly if you combine it by making the _service field readonly.

Whether or not you use a DI Container to resolve dependencies is a perpendicular matter.

There are, in my opinion, no advantages of using a Service Locator over Constructor Injection.

Mark Seemann
+1 - great answer (I particularly like the mention of a guard clause). Note also that code using a Service Locator is coupled to the locator class.
TrueWill
+1, that last point is crucial - if there were some advantage to using global state, there might be a reason to consider it, but there isn't. On small projects with tight teams, service locator seems fine, but it shows its weaknesses very fast. The biggest problem is that it is "difficult to specify the pre and post conditions for the client object's interface, because the workings of its implementation can be meddled with from outside." (From http://accu.org/index.php/journals/337, one of my favorites among the usual anti-singleton articles)
Jeff Sternal
But if I user Constructor injection my code dependent of the DI Container infrastructure.
dotneter
@ais: Constructor Injection doesn't make your code dependent on any particular DI Container. Only in the application's root must you resolve all dependencies. You can use a DI Container to do this, or you can implement it yourself. This SO answer explains this in a little more detail: http://stackoverflow.com/questions/1410719/design-where-should-objects-be-registered-when-using-windsor/1410738#1410738 Although that question asks about Windsor, the argument applies to any DI Container.
Mark Seemann
What if need create new instance of A per method call and I don't what than class B dependent of IService.class B{ void Test() { A a = Container.Resolve<A>(); }}
dotneter
@ais: I don't understand all of your question, but if you need to create a new instance of A per method call you could define an interface that defines a factory of A - then make that factory your dependency instead of A itself.
Mark Seemann
+1  A: 

The Global one is a service locator. Martin Fowler compares and contrasts the two patterns far better than I ever could in his article Inversion of Control Containers and the Dependency Injection pattern.

Personally I'd go with DI.

TrueWill
A: 

Using a DI Container

  • -ve Learning curve to begining to use one
  • -ve Might be overkill depending on the size of your project
  • +ve Usually means writing less boilerplate code
  • +ve Often have mechanisms in place to handle object life span for you (singleton, caching strategies etc)

In your global scenario:

  • -ve When looking at the constructor through intellisense the immediate dependencies it has are unknown. Don't forget, each one of the classes it depends on will probably have dependencies of their own.
  • -ve Harder to setup during test, are you sure you've setup all the necessary objects in the Config ?
  • +ve Simpler to get started with
Kirschstein