Dependency injection is all the rage now on Android but there are also some other patterns worth considering for managing dependencies. A Service Locator is a very simple pattern which can be implemented in few lines (50?) of code and can sometimes be a viable alternative or complement to DI frameworks like Dagger 2.
The Service Locator is considered by some (or many) as an anti-pattern, but I would argue that you always have to choose the right tool for the job (and it’s good to have a variety of tools in your toolbox). In some cases the Service Locator can be an extremely simple and efficient solution. It can also get handy in case you have constraints like APK size, method count, build speed or overall complexity.
If you would like to learn more then there is a famous article about the Service Locator and Dependency Injection and their differences and use cases from Martin Fowler and I highly recommend it. Also there is an article from Guy Murphy that sheds a different (more positive) light on the service locator anti-pattern debate.
How does Service Locator help you?
- It creates and keeps an instance of your (service) class in a central registry
- It allows you to switch different implementations of one interface at runtime (good for testing or switching implementation based on device)
- * On Android, it can also “inject” the ApplicationContext to your class
In a way it’s like using Dagger where all your components are
ApplicationScope. It has no notion of Scopes, every instance is application wide. A Service Locator is especially good for stateless and/or classes that are able to store their state and survive process death on Android. This can be your RestService, DBService…
Let’s say we have an interface called
IRestService and we have two implementations. One is the real
RestServiceImpl and the second one
MockRestService is a mock implementation for our junit tests.
Within the code we should be able to get the current implementation by simply calling
SL.get(IRestService.class), prompting the locator to lazily create an instance of this class based on the current configuration (real or mock), return it in the call and store it for further retrieval.
Much like Dagger — a good place to configure the Service Locator is inside your Application.onCreate(). The Service Locator needs to know what kind of implementation classes it should use. The configuration can be as simple as this:
SL.bindCustomServiceImplementation(IRestService.class, RestService.class)(for jUnit you would override this).
A simple Android implementation
Here is a simple single class implementation.
I have added one small “twist” to the basic Service Locator implementation. On Android often times you need to access the Application Context and we can inject this into your constructor. For this implementation we only allow classes with empty or single Context parameter constructors.
The interface (e.g.
IRestService) needs to extend the empty
IService interface. This has no functional impact but it’s easier to quickly find interfaces / classes that are meant to be used with the Service Locator.
You need to call
SL.init(this) to set the Application Context. This Context instance will then be delivered through your constructor (only in case your constructor takes a Context).
Use with caution
As with most things in life — use your own judgement and use with caution. I’m definitely not promoting this as a silver bullet solution and replacement for Dagger (which it definitely isn’t). This is merely an extremely simple solution to a small range of problems and it may be suitable for some types of projects. Of course it has it’s own set of problems, the cross-dependencies (when one service has a dependency on another one) can get complicated and testing is less flexible than a full fledged DI.
Example implementation: https://gist.github.com/DanielNovak/06bc27fa4ecea63207c424bef88199df