Anti-Pattern or not?

Not to start so negative, but a lot of people might not be interested in this pattern as it has in the past been regarded as an anti-pattern, but implemented correctly and not abused, a Service Locator pattern can be the answer to a number of problems.

Pros & Cons

Some of the problems that a Service Locator can solve are:-

  • Centralised Services – I have seen this time and time again. When working on a large legacy application, there has been a number of times a very simple service, such as logging have 3 or 4 different implementations. Maybe they’re labelled differently, in different packages, or just quite obscure to understand. Having a Service Locator that has some core services in, maybe one labelled logging can help the visibility of certain services and offer a centralised location for one tool. When created correctly, I.e. the actual implementation hiding behind an interface offered by a Service Locator, we can easily switch out the implementation or library of the logging class, refactor it and the entire app would function as normal with the new logging service in place.
  • Readily available services – When using a Service Locator we can create instances of various services and have then readily available to use when called upon. There is an agrugment against this approach, which I will explain in further detail in the post. By having services ready to use we can save the launch time of each service being created over and over again.
  • Easy Implementation – This is subject to situation, but a logical step that can be taken to when working with legacy code is using a Service Locator. We can create services that should be used in multiple locations and add them to a Service Locator. By doing this we can establish that the service functions correctly, can be easily switched out for a mock instance,are fully tested and are hidden behind an interface, we can replace all direct calls to this actual instances of the service class in question, and we have now a logical way of injecting mock instances.
  • Simple Code/Simple to understand – Given that a Service Locator is a pattern rather than a library, using minimal programming experience it’s quite easy to create a Service Locator that will contain various services. Each service should return it’s interface so that we can control dependency inversion and each service knows how to create itself.
  • Unit Testable – Given that this Service Locator is simple a container of services, it’s extremely easy to mock and ensure that each service returned can be mocked.

via GIPHY

So there’s our pros, what about the cons?

  • Hidden Dependencies – By adding all services to a Service Locator we are actually hiding the dependencies that each service depends on. This can always cause confusion as it doesn’t allow developers to actually understand what is happening underneath the surface. Debugging becomes quite difficult sometimeServices.
  • Dependency on Service Locator – By adding a Service Locator we are actually adding a dependency. It’s not true constructor injection, it’s not true dependency injection, it’s simply just depending on a Service Locator. This is partially why it can be called an anti-pattern, it was originally designed to solve dependencies, but is one in itself.
  • Memory issues – What happens when a Service Locator becomes to bulky, to many services added, too many Services Locators added. We will be creating classes that might not be even used anymore for the entirey of the applications life. This is a terrible idea.
  • No Service Protection – Unless you add some kind of check, a Service Locator can end up containing a bunch of services, which shouldn’t be available to particular parts of your app. In most DI libraries I have used, you create modules and compontents that are only available to certain classes. This keeps some encapsulation of certain services.
  • SOLID/OOP – I don’t really need to go into detail on this, but it obviously is breaking OOP laws and SOLID principals.

via GIPHY

Implementation

Now that I have established some pros and cons, I cannot stress enough, it is an anti-pattern when used incorrectly and in the incorrect scenario. Therefore, logically placed, and logically planned you can create something that follows a Service Locator pattern without actually creating too much of a code smell. Here’s an example of how we’ve approach this in the past. I will demonstrate a very simple Kotlin based Service Locator.

import android.content.Context
import androidx.annotation.VisibleForTesting

interface ServiceLocator {

    companion object {
        private val LOCK = Any()
        private var instance: ServiceLocator? = null

        fun instance(context: Context): ServiceLocator {
            synchronized(LOCK) {
                if (instance == null) {
                    instance = CoreServiceLocator(context.applicationContext)
                }
                return instance!!
            }
        }

        @VisibleForTesting
        fun swap(locator: ServiceLocator) {
            instance = locator
        }
    }

    fun getLogger(): Logger
    fun getAnalytics(): Analytics
    fun getPreferences(): UserPreferences
    fun getEnvironment(): EnvironmentConfig
}

private class CoreServiceLocator(val applicationContext: Context) : ServiceLocator {
    
    private val loggingService: Logger by lazy {
        LoggingImpl()
    }
    
    override fun getLogger(): Logger = loggingService

    private val analyticsService: Analytics by lazy {
        AnalyticsImpl(applicationContext)
    }

    override fun getAnalytics(): Analytics = analyticsService

    private val userPrefsService: UserPreferences by lazy {
        UserPreferencesImp(applicationContext, environmentService)
    }
    
    override fun getPreferences(): UserPreferences = userPrefsService

    private val environmentService: EnvironmentConfig by lazy {
        EnvironmentConfigImpl()
    }
    
    override fun getEnvironment(): EnvironmentConfig = environmentService
}

via GIPHY

Obviously, this isn’t perfect by any means, and it doesn’t replace all the capabilities of many popular DI llibraries but it’s a simple solution that works. In all the UI/Unit tests I have created since trying this pattern in certain scenarios, it’s helped us fully test everything without any drama. I’d be keen to hear of any imporvements people can add or what other approaches people have taken.

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *