An introduction to the Service Locator Pattern

The Service Locator pattern abstracts the mechanics of creating objects. Rather than directly instantiating a dependency, an object asks a ServiceLocator for an implementation of an interface.

Consider the following legacy code.

    public class Client {
        public void Consume() {
            var service = new Service();
            service.Provide();
        }
    }

    public class Service {
        public void Provide() {
            //Implementation goes here
        }
    }

The Client instaniates an instance of Service and holds it within a method variable. There are no seams within this code. The Client is tightly coupled to the Service.

Now consider the following test-driven code.

    public class Client {
        private readonly IService service;

        public Client(IService service) {
            this.service = service;
        }

        public Client() : this(new Service()) {}

        public void Consume() {
            service.Provide();
        }
    }

    public interface IService {
        void Provide();
    }

    public class Service : IService {
        public void Provide() {
            //Implementation goes here
        }
    }

The Client now uses Poor Man’s Dependency Injection to instantiate and hold an implementation of IService within a class member variable. This pattern, often used during Test Driven Design, allows the Client to be exercised with a different implementation of IService. A test double can be injected into the Client to provide known behaviour and assert expectations. Despite this the Client remains coupled to the Service.

Finally, consider a simple implementation of the Service Locator pattern.

    public class Program {
        private static void Main(string[] args) {
            ServiceLocator.Register(new Service());

            var client = new Client();
            client.Consume();
        }
    }

    public static class ServiceLocator {
        private static readonly Dictionary<Type, object> services = new Dictionary<Type, object>();

        public static void Register<T>(T service) {
            services.Add(service.GetType(), service);
        }

        public static T Resolve<T>() {
            return (T) services[typeof (T)];
        }
    }

    public class Client {
        private readonly IService service;

        public Client(IService service) {
            this.service = service;
        }

        public Client() : this(ServiceLocator.Resolve<IService>()) {}

        public void Consume() {
            service.Provide();
        }
    }

    public interface IService {
        void Provide();
    }

    public class Service : IService {
        public void Provide() {
            //Implementation goes here
        }
    }

Rather than instaniating an instance of Service, the Client asks for an implementation of IService. The ServiceLocator is initialized with an instance of Service during application start up but it is easy to see how a different implementation could be introduced without affecting the Client. The Client and Service are now loosely coupled.

In reality, it’s unlikely that you’d use such a simple ServiceLocator. It’s more likely the ServiceLocator would be a facade in front of a Container that creates and manages objects with some degree of sophistication.

Service Locator Pattern separates instaniation from consumption. It encourages applications composed of loosely coupled components, introducing application-wide seams that allow us to change or extend the default implementation of certain services. It is rare that we really need an open-closed architecture, let alone a pluggable one. Better to keep things simple and refactor when necessary rather than over-engineer. However, the ability to strategically introduce stubs or mocks deep into a call stack to facilitate testing is a compelling reason to introduce the pattern.

Of course, the same is true of Dependency Injection. It is an elegant pattern that ensures coupling is kept to a minimum. All objects are given their dependencies rather than asking for them – facilitating testing at many levels. However, Dependency Injection isn’t something to be considered lightly. It is a pervasive pattern that requires a good understanding of object lifetime, often hard to integrate with frameworks that rely on reflection or configuration to instantiate objects.

In practice, the extra gain provided by Dependency Injection is often wasted. Applications rarely need the ability to substiute every object. Better to use pragmatic patterns to achieve the desired effect – especially when working with a legacy code base.

Service Locator Pattern is to components what Poor Man’s Dependency Injection is to classes. It allows us to raise the abstraction level of our tests, to thoroughly exercise an application without external dependencies or physical constraints, to write system or scenario tests.

Advertisements
4 comments
  1. Christophe said:

    Explained nicely…keep up the writing

    • Joe Campbell said:

      Thanks Christophe

  2. thehumbleprogrammer said:

    Nice article, explains the pattern nicely.

    The downside of using the service locator is that it is very easy to misuse. If the team you are working on is not careful about dependencies the service locator can hide the number of collaborators a class has. Constructor injection is more explicit in this regard as you can see in your unit tests the number of dependencies a class has.

    I would also question the need to substitute a dependency deep down in the call stack. This to me seems like the unit test is not focused enough, unless you are doing integration tests and as we all know integration tests are a scam 😉

    Just my two cents.

    • Joe Campbell said:

      I agree, SLP can be abused if it is used randomly within a class. That’s why I prefer to use it in combination with poor man’s dependency injection. That way an object still advertises it’s dependencies and you don’t have to spin up a container for unit testing. This relies upon good practice with the development team but, then again, most of our practices do.

      I think there is a need to inject a dependency deep into the call stack for testing but quite what that testing is called I’m not yet sure. It’s not unit testing unless you consider a service or component a unit. I guess it is integration testing if you use the definition that it involves more than one service.

      Recently, I’ve been writing ‘big tests’ that exercise scenarios rather than objects. Yes, they’re closely related to integration tests (they can even share an abstract scenario builder) but they never cross a physical boundary and they always run under CI. They aren’t long running therefore the feedback is quick, they aren’t brittle therefore they don’t add noise, they use the builder pattern so the test setup is reduced.

      I’m not sure about the argument regarding the number of assertions being too few or too great. I mostly treat them as black box tests, asserting only the inputs and outputs of a service. However, I will assert that a method is called on an object responsible that, for example, persists to a database or calls an external service – hence the need for DIP/SLP.

      These style of tests don’t cover every single code path but they do explain the first class concepts of a system. If BDD style tests use the domain language and TDD style tests use the language of the objects then these tests use system language. Quite often new members of a team will ask what a component does but, while unit tests are a lot better than nothing, it can be hard to see the wood for the trees. Using the Given, When, Then format (on a ScenarioBuilder) allows readers of the code to immediately understand what the system functionality is.

      Borrowing the FitNesse motto, these style of tests are “about building the right code, as opposed to unit-level testing, which is about building the code right”.

      You may have guessed, I have a post almost ready about this

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: