Wednesday, September 22, 2010

ServiceLocator vs Dependency Injection

 

During the past weeks I have been trying to figure out if I should go with the DI(Dependency Injection) pattern or the SL (Service Locator) pattern.
I assume here that the reader know the difference between the two.

I have been using the SL pattern for years know and it has proved to be very valuable.

But guess what? Now they tell me that it is considered an anti-pattern. Have a look here

What really bugs me is that all DI examples including the example from Mark Seemann, are so naive and simple.

Sure if you have InterfaceA and InterfaceB, you can live with passing an implementation of InterfaceB to the constructor of  ClassA.

But graphs usually gets a little more complex that and if we follow the Soc principle, we might end up with a lot of dependencies in order for ClassA to actually do its magic.

The result of this is that the consumer of ClassA must manually wire up a dependency map before calling the constructor passing the root dependencies for ClassA.

To illustrate this we are going to use Mark’s example.

    public interface IOrderProcessor
    {
        void Process(Order order);
    }

    public class OrderProcessor
    {
        private IOrderValidator _orderValidator;
 
        public OrderProcessor(IOrderValidator orderValidator)
        {
            _orderValidator = orderValidator;
        }
    }

In order to use the order processor class we need to do the following:

OrderProcessor orderProcessor = new OrderProcessor(new OrderValidator());

That does not look to bad at all.

The OrderProcessor clearly states that it has a dependency upon an IOrderValidator implementation.

The question is… Do I really need to know about this dependencies? Let that rest for a minute while we expand the example.

Lets just quickly review the example from Mark.

public void Process(Order order)
{
    var validator = Locator.Resolve<IOrderValidator>();
    if (validator.Validate(order))
    {
        var collector = Locator.Resolve<IOrderCollector>();
        collector.Collect(order);
        var shipper = Locator.Resolve<IOrderShipper>();
        shipper.Ship(order);
    }
}

He only shows the SL variant of the example and I can’t help but wonder why?

Anyway, we see from the implementation that two more dependencies have been added to the OrderProcessor.

An interesting observation here is that the dependencies are only needed if the order is valid…….

But let’s stick with the DI approach and then we must add two more parameters to the OrderProcessor class.

    public class OrderProcessor
    {
        private IOrderValidator _orderValidator;
 
        public OrderProcessor(IOrderValidator orderValidator, IOrderCollector orderCollector, IOrderShipper orderShipper)
        {
            _orderValidator = orderValidator;
        }
    }

To use the OrderProcessor we now need to do this:

OrderProcessor orderProcessor = new OrderProcessor(new OrderValidator(), new OrderCollector(), new OrderShipper());

In addition to introducing a breaking change this also starts to look a little bit messy, wouldn’t you say?

We can also see here that we create OrderCollectior and OrderShipper instances regardless of the result from validator.Validate(order).

Lets take this even further by adding some logging capabilities.

All classes should have logging enabled and that means even more breaking changes.

ILogger logger = new Logger();
OrderProcessor orderProcessor = new OrderProcessor(new OrderValidator(logger), new OrderCollector(logger), new OrderShipper(logger));

I don’t know about you, but to me this starts to look a little complicated.

We might think of a solution where we let the OrderProcessor class create its own Logger, but that would be hiding a dependency, right?

Let’s get back to the question ..”Do I really need to know about this dependencies?”

I tell you what.. I just need to process orders and don’t really care that much how much magic is needed to actually do that.

If we review the actual contract (IOrderProcessor) involved to process orders it does not say anything about collectors and shippers.

So why should I care?

Do I really need to inject an IEngine instance before I can drive?

Yes, I would have to if I was the constructor of the car, but in this case I am just the consumer, see?

Remember Cole Trickle (Tom Cruise) from “Days of Thunder” .. (Yes, I’m actually that old)

"They told me to get in the car and drive and I could drive......but I don't know much about cars, o.k?!

What if the engine suddenly got equipped with a turbo?

So what? I DON’T CARE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! I just want to drive!!

To solve this nightmare of manual wiring we can use an Ioc container.

Given that the container is configured it will happily resolve all dependencies for us.

The configuration of the container remains pretty much the same whether we use DI or SL so that is not an argument.

I feel that DI is somewhat of a “all or nothing” solution.

In order for the container to resolve our dependencies we must use this pattern from the get go. Otherwise we are faced with manual dependency injection which I think we all can agree can become pretty messy.

Feel free to leave a comment, I would love to hear your opinion

Regards

Bernhard Richter

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3 comments:

ploeh said...

Thanks for posting this.

The reason I didn't show the idiomatically correct ways to implement OrderProcessor was that it was ground I had already covered at that time.

Cross-cutting concerns, such as logging, are better implemented as Decorators unless you choose to go all the way and use dynamic Interception.

However, I certainly agree that the entire object graph quickly becomes complex, which is why use of a good DI Container is very helpful - but remember: don't call the container; it'll call you :)

Bernhard Richter said...

I see what you mean and I also see how pure DI can make the overall design of the application better.

On the other hand, I would like to go back to the example and ask this:

Isn't it unnecessary to eagerly create OrderProcessor and OrderShipper instances if the order is not valid?

I just sometimes feel that dependencies should not always share the same "lifecycle" as the injection target.

What do you think?

ploeh said...

Oh, we already dealt with that question here and here :)