The service locator behavioral pattern allows you to make use of an interface without knowing what class implements the interface.
This design pattern shares many aspects with the factory creational pattern. Both patterns allow you to use an interface without knowing exactly what class implements the interface. But while the factory design pattern is more focused on creating instances, this pattern is more focused on locating instances that already exist.
Maybe you have some code that handles logging and you use this code throughout your application. You’re not going to want to pass a logging instance to every method you call so that means a lot of places in your code that will need some way to find a logger. Without this design pattern, you might consider making the logger a singleton. But this requires that your code know exactly which singleton to use to get to the logger.
Instead of going directly to the logger, consider instead asking another class for the logger. This other class will be the service locator. And, yes, this means that you just traded one singleton for potentially another singleton. The point is though that your code is no longer coupled in all those places to a specific logger. It’s now coupled to a specific service locator. It uses the service locator to ask which logger it should use.
This gives you the ability to easily swap one logger for another one. You’ll still have problems if you want to swap out one service locator for another one. You’ll probably want to avoid doing that. And asking another service locator for a service locator is also a bad idea. Just stick with a single service locator and let it help you manage the various services needed though your application.
Listen to the full episode about this design pattern, or you can also read the full transcript below.
The basic description says that this pattern provides global access to a service without coupling your code to the concrete implementation.
There’s a couple things that need explaining right away. First of all, what is a service and how do you use it. Well, a service can mean many things. Any code that can be called to perform some action or return some result can be thought of as a service. But that’s not what we normally think of as a service.
A service is usually some useful and often unrelated functionality that your code needs. When I say unrelated, I don’t mean that the service has no meaning to your code. I just mean that the service provides some functionality whether your code calls it or not.
You could have a number adder service that accepts two numbers, adds them for you, and returns the result. This would be rather pointless unless there was something special that the number adder service could provide. Maybe it could allow you to add really large numbers. Numbers to big for your platform to handle on its own. Now we’re getting somewhere.
Sometimes we think of a service as a web service. This is functionality that you can access over a network. Usually the internet. The service will likely have access to special data such as map coordinates or weather information.
A more common example though is logging. It’s very useful to be able to record events that happen while your program is running. Because if there are any problems, the log file can help a developer understand what might have went wrong. Logging is a great example for a service because it’s needed throughout your application and you need some way to get to it.
Logging is interesting because it’s often included in your program as a library. This means the code exists and is available to call just like any other code in your application that you write.
In all these cases, how your code accesses the service is important because you may need to access the service quite often as in the case of logging. Or maybe not so often but the service could live somewhere that is subject to change. Maybe the number adder service goes out of business because your application was their only customer. You don’t want to have to change your code in order to find a new provider of the service. Even logging could need changes if you decide to switch to a new library.
The other thing I wanted to point out has to do with the global access. that might sound familiar if you’ve already listened to episode 60 about singletons. Anytime you need to access some common or limited service such as logging, then you might consider making the logging class available through a singleton. After all, you probably only want just one log file, right? While the singleton pattern does help solve the problem of how to get access to something, it doesn’t help decouple your code from the provider. You still have to know about the singleton and how to get it. It’s like this. You know exactly what class implements the methods you want to call. And you know exactly how to get hold of the instance of the class that implements these methods. The singleton pattern has it’s share of problems so make sure to listen to episode 60 for more information.
This pattern shows you how to make use of a service without knowing anything about what class is providing the service. I’ll explain more right after this message from our sponsor.
( Message from Sponsor )
The concept of a service locator is actually really simple. And we use them everyday in real life. When you need to add gas to your car and need to ask for directions, do you ask for a specific company name? Well, maybe. But most of us probably just ask for the nearest gas station. We identify the type of service we need and let another person suggest a place that can meet that requirement. Same thing if we need to ask for the nearest pizza shop.
We could end up asking another person, or we could use a search engine on our phone, or we could use a directory listing phone book. Whatever the source of the recommendation, this person, website, or book helps us locate a service. And the same thing can be used in code.
You still need to figure out how you’ll get access to the code that you’ll use to then ask for a service. This would be a better use for a singleton.
But what does this really provide for you. Doesn’t it just trade one singleton for another? Well, remember that you don’t have to use singletons. You do need some way to get to the locator though. And are you really better off by going through a locator instead of directly using, for example, the logging code?
I already mentioned what happens if you need to swap logging with some other library. This pattern gives you some extra benefits that can be really useful.
Let’s say that you need to log two different formats for a while. Maybe your manager wants you to gather all the log messages for just the next two weeks that everybody on your development team generates while testing a new feature. How would you do this?
You could modify the logging code itself to not only write messages to a file but also send them to a common database. But this requires that you understand how the logging code works and have access to the source code. If you’re using a logging library that you don’t have the source code for, then you won’t be able to change it. Without this pattern, you could be looking at changing all the places in your code that instantiates the logging class to instead create a new class with the same interface but that does the extra gathering. If this sounds like the decorator pattern from episode 67, then you’re right.
Instead of modifying all that code, which is especially painful when it all needs to be put back the way it was two weeks later, a better way would be to avoid coupling the code directly to the logging class in the first place.
Sure, it’ll need to be coupled to something. If that something is a locator class, then it’s still easy to ask the locator for the logger. It’s just a little extra diversion throughout the code to ask for a logger instead of going straight to whatever logger the code thinks it should be using.
If you have this in place, then you just implemented the service locator pattern. Now, when your manager needs you to change how the logging is done, you can go to the one place in the locator where it creates the logger and change it to return something different. It can still use the decorator pattern. That’s actually a really good use of the service locator. It can return something that meets all the needs of the calling code but that internally has been decorated one or more times to do other things.
Before we end, let’s get specific. How does your code ask a service locator for some kind of service? Your code will first need to get access to the service locator itself and this could be through a singleton or through static methods.
Once you have the locator instance or decide to go through static methods, you have a couple choices. One method is to create specific methods in the locator that know how to return that type of service. For the logging example, this would mean your locator has a method called getLogger. The advantage of this approach is that it’s very clear what services your locator knows how to provide by just looking at the available methods. If you see a method called getLogger, then you know that the locator can find a logging service.
The disadvantage though is that anytime you want to use the locator to find some new service, you have to add a new method. This can cause the number of methods to increase and it becomes harder to maintain. Another choice you have is to make a single method that can return any type of service. You could identify the service you want by a string with the name of the service or a number that matches some well-known services. By well-known, I mean that the numbers are well-know to your application. There isn’t some number system that matches services for everybody.
Like any engineering choice, this approach also has a tradeoff. With the general method that returns any service based on an identifier, you no longer get a specific return type that matches the service. I mean, if you ask a method called getService for a service with the name “Logger”, then it’ll find the service but it can’t return the service to you as a logging interface. It’ll be some generic service interface. And what do you do if your code misspells “logger”? You may decide it’s better to have specific methods instead of a more general getService method.