Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

The Service Locator Pattern

DZone's Guide to

The Service Locator Pattern

Need to understand the service locator pattern? Here's an intro tutorial with background, how to use the code and more.

Free Resource

Share, secure, distribute, control, and monetize your APIs with the platform built with performance, time-to-value, and growth in mind. Free 90-day trial of 3Scale by Red Hat

In this article, we will try to understand the service locator pattern. We will also implement a contrived implementation to demonstrate the service locator pattern.

Background

Whenever we have a scenario where one class is providing some functionality and another class want to use this functionality, the simplest way to achieve this would be to instantiate the class providing the service in the client class and use it.

For instance class A that want to call a method of class B, we can simply have an object of B inside A and call its methods whenever we need to. The code will look something like following:

public class B
{
    public void DoTaskOne()
    {
        Console.WriteLine("B.DoSomething");
    }
}

public class A
{
    private B b;

    public A()
    {
        b = new B();
    }

    public void GetOneDone()
    {
        b.DoTaskOne();           
    }
}

This approach of having the class instances contained inside other classes will work but it has some downsides. The first problem is that each class needs to know about every other class that it wants to use. This will make this application a maintenance nightmare. Also, the above approach will increase the coupling between the classes.

From the best practices’ perspective, whenever we are designing our classes we should keep the dependency inversion principle in mind. Dependency Inversion Principle says that the higher level modules should always depend on abstractions rather than lower level modules directly. So we should always design our classes in such a way that they always depend on the interfaces or abstract classes rather than other concrete classes.

So the classes we saw in the above example will change. We first need to have an interface that A can use to call DoTaskOne. Class B should implement this interface. The new classes will look like following:

interface IDoable
{
    void DoTaskOne();
}

public class B : IDoable
{
    public void DoTaskOne()
    {
        Console.WriteLine("B.DoSomething");
    }
}

public class A
{
    private IDoable doable;

    public A()
    {
        // How to create the doable object here???
        // doable = new B();
        // This seems wrong
    }

    public void GetOneDone()
    {
        doable.DoTaskOne();
    }
}

The above code shows the classes perfectly designed where the higher lever modules depend on abstractions and the lower level modules implementing these abstractions.

But wait…

How are we going to create and object of B? Should we still do that as we did in the previous code (i.e. doing a new on B in the A’s constructor)? Would it not defeat the whole purpose of having loose coupling?

The first answer to this question would be to implement a factory pattern. Since the Factory pattern totally abstracts the responsibility of creating classes from the client classes, we could have a factory class that could create the concrete instances of type IDoableand the class A can use this factory to get the concrete implementation of IDoablewhich is class B in this case. So with factory implementation in place our code will look something like following:

public class DoableFactory
{
    public B GetConcreteDoable()
    {
        return new B();
    }
}

// Constructor of A
public A()
{
    DoableFactory factory = new DoableFactory();
    doable = factory.GetConcreteDoable();
}

Now we will not discuss the details for Factory pattern in this article but I strongly recommend that you get yourself acquainted with this pattern before proceeding further. You can find more about factory pattern here: Understanding and Implementing Factory Pattern in C# [^]

Why are we talking about Factory Pattern?

So one might wonder why are we talking about factory pattern when our goal was to talk about service locator. Well, the answer to this question lies in the fact that from the calling code’s perspective both patterns are same. With the use of factory we have solved the following problems:

  • We have inverted the control between client and the service class.
  • We have client code depending on abstraction rather than actual service implementation (i.e. loose coupling).
  • We have a way to hook up the concrete implementations with the interface using the factory class.

But there are few more issues one should look at before deciding to go with the factory pattern. Since the factory class is returning the new instance of the requested object, there are few things we need to ask.

  • What about the cost of construction? If this class that we are creating inside the factory is very expensive to create, is it a good idea to let the client use the factory and create as many instances as they need.
  • What should be done when we already have an instance ready for the client to be used (i.e. we don’t want to instantiate a new class but rather return an existing instance of the object)?
  • What about the ownership? Since the factory class return a new instance to the caller client, the calling code/class owns the instance. What if the ownership lies with someone else (this point is somewhat redundant as the previous point is also the same i.e. returning an existing instance owned by someone else).

Now if we look at the above questions, we might find ourselves needing something other than a factory which instead of returning a new instance returns an existing instance of an object from the system. And this is where service locator comes in picture.

Using the Code

So from the above discussion, it is clear that we need Service locator instead of a factory whenever we want to ‘Locate’ an existing object/service and return it to the caller. And the client code does NOT own the returned object/service but instead only going to use it.

From this discussion, it is clear that we need to create a Service Locator class that is capable of

  1. Letting the application register the concrete implementations(objects/services) for a given contract(interfaces).
  2. Let the calling client code get the concrete implementation(object/service) using a contract(interface).

Let us try to look at this in action using a very contrived example.

The example we will be working on will be a simple audio file manager which let the user manage a given audio file. One part of this application is to be able to play the file being managed. The way we will implement this is that we will have an ApplicationFacadeclass that will handle the play request from the presentation layer (console in this case). This facade will then use the IPlaybackServiceinterface to request the playback. Let's say that the current playback is being handled by DirectX and we have a concrete class for playing an audio file using DXPlaybackService. But we want the application to be extensible to support other playback engine (lets say LibVLCPlaybackService) later.

Now the reason for using a service locator, in this case, is that creation of playback service is an expensive operation and we don’t want the application to be able to create the instance whenever the operation is requested since this will create a delay between the play request from a user and actual playback. Also, we want the have only one instance of the engine in our application which will be returned to the calling code.

Let us look at how this application is designed. Let's start by looking at the model that encapsulates the audio file.

public class AudioFile
{
    public string Title { get; set; }
    public string FilePath { get; set; }
}

Now that we have the data structure to hold the audio file, let's look at the interface that all the playback services should conform to.

public interface IPlaybackService
{
    void PlayFile(string filePath);
}

Now let's try to create the Concrete service class that will use DirectX to play the audio file(given a file path).

public class DXPlaybackService : IPlaybackService
{
    public void PlayFile(string filePath)
    {
        Console.WriteLine("DirectX is being used to play {0}", filePath);
    }
}

Now that we have all the playback related data structures and services in place, let's try to write our application facade class in such a way that it will use a service locator to find the concrete instance of playback service and use it to perform playback.

public class ApplicationFacade
{
    AudioFile m_Audiofile = null;
    IPlaybackService service = null;

    public ApplicationFacade(AudioFile file)
    {
        m_Audiofile = file;
    }

    public void Play()
    {
        Console.WriteLine("Requesting playback for {0}", m_Audiofile.Title);
        service = ServiceLocator.GetService < IPlaybackService >();
        if(service != null)
        {
            service.PlayFile(m_Audiofile.FilePath);
        }
    }
}

We still have not created the ServiceLocatorclass. So let's create a simple service locator that will have the possibility of registering and retrieve the concrete service using the interface.

public class ServiceLocator
{
    static Dictionary < string, object > servicesDictionary = new Dictionary < string, object >();

    public static void Register < T >(T service)
    {
        servicesDictionary[typeof(T).Name] = service;
    }

    public static T GetService < T >()
    {
        T instance = default(T);
        if(servicesDictionary.ContainsKey(typeof(T).Name) == true)
        {
            instance = (T) servicesDictionary[typeof(T).Name];
        }
        return instance;
    }
}

With this, we have a rudimentary service locator implementation in place. Now lets try to simulate the user interaction with the application to all this in action. Following things should happen when a user runs the application.

  1. We will create the available playback service at the time of application start.
  2. We will register that service with our service locator class.
  3. User will select the audio file for playback.
  4. The ApplicationFacade will use the service locator behind the scenes to get the handle to concrete service and play the audio file.

Following code mimics all the above-mentioned steps:

static void Main(string[] args)
{
    // Lets use the DirectX service to play the file
    IPlaybackService playbackService = new DXPlaybackService();

    // First let us register our audio playback service with service locator
    ServiceLocator.Register < IPlaybackService >(playbackService);

    // Lets try now to mimic an audio file playback
    // Let the user select a file
    AudioFile file = new AudioFile
    {
        Title = "Dummy File",
        FilePath = "C:\\DummyFile.wav"
    };

    // Lets instantiate our application facade passing the audio file to it
    ApplicationFacade facade = new ApplicationFacade(file);

    // Lets Emulate the user request for playback
    facade.Play();
    Console.ReadLine();
}

When we run the application we can see that the DirectX will be used to play our audio.

servicelocator 1

Now let's say at some later point we decide to use LibVlc for playback and write a service for that.

public class LibVlcPlaybackService : IPlaybackService
{
    public void PlayFile(string filePath)
    {
        Console.WriteLine("LibVlc is being used to play {0}", filePath);
    }
}

Now the only thing that needed to be changed is to create the instance of LibVlcPlaybackServiceand register it with our service locator. Following code shows that version of our mimicking code.

static void Main(string[] args)
{
    // Lets use the LinVlc service to play the file
    IPlaybackService playbackService = new LibVlcPlaybackService();

    // First let us register our audio playback service with service locator
    ServiceLocator.Register < IPlaybackService >(playbackService);

    // Lets try now to mimic an audio file playback
    // Let the user select a file
    AudioFile file = new AudioFile
    {
        Title = "Dummy File",
        FilePath = "C:\\DummyFile.wav"
    };

    // Lets instantiate our application facade passing the audio file to it
    ApplicationFacade facade = new ApplicationFacade(file);

    // Lets Emulate the user request for playback
    facade.Play();
    Console.ReadLine();
}

And now when we run the application we can see that the LibVlc is being used for playing our audio.

servicelocator 2

The important thing to note here is that we didn’t have to change the caller code i.e. ApplicationFacadeto use the new service.

Points of Interest

One of the important points to remember here is that service locator should be used over factory when we want to locate and return an existing instance of object/service to the caller and we don't want the caller to have the ownership of returned objects. In our example, we are creating and registering new services in the code but that is for the demonstration purpose only. we could create new services in separate DLLs and put the service registration in the config file (and service locator takes care of using the config file to determine the service to be used). Or we could let the user explicitly register the services at the application start like a few ORMs and IoC containers do. This article has been written from beginner’s perspective.

I hope this has been useful.

Explore the core elements of owning an API strategy and best practices for effective API programs. Download the API Owner's Manual, brought to you by 3Scale by Red Hat

Topics:
java ,service ,pattern ,factory pattern ,c#

Published at DZone with permission of Rahul Rajat Singh, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}