Archive

Monthly Archives: October 2009

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