Click here to Skip to main content
15,881,812 members
Articles / Programming Languages / C#

A Beginner's Tutorial For Understanding and Implementing Service Locator Pattern in C#

Rate me:
Please Sign up or sign in to vote.
4.77/5 (7 votes)
19 Jan 2016CPOL8 min read 14.5K   96   7   3
In this article we will try to understand the service locator pattern.

Introduction

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.

C#
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.

C#
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? But would it not defeat the whole purpose of having loose coupling?

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

C#
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 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 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 ApplicationFacade class that will handle the play request from the presentation layer(console in this case). This facade will then use the IPlaybackService interface to request the playback. Lets 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 application to be able to create the instance whenever the operation is requested since this will create a delay between the play request from 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. Lets start by looking at the model that encapsulates the audio file.

C#
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, lets look at the interface that all the playback services should conform to.

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

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

C#
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, lets 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.

C#
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 ServiceLocator class. So lets create a simple service locator that will have the possibility of registering and retrieve the concrete service using the interface.

C#
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.

C#
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.

Image 1

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

C#
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 LibVlcPlaybackService and register it with our service locator. Following code shows that version of our mimicking code.

C#
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.

Image 2

The important thing to note here is that we didn't had to change the caller code i.e. ApplicationFacade to use the new service.

Points to 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 dont want the caller to have the ownership of returned objects. In our example we are creating and registering new services in code but that is for the demonstration purpose only. we could create new services in separate DLLs and put the service registration in 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.

History

  • 20 January, 2016 - First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India

I Started my Programming career with C++. Later got a chance to develop Windows Form applications using C#. Currently using C#, ASP.NET & ASP.NET MVC to create Information Systems, e-commerce/e-governance Portals and Data driven websites.

My interests involves Programming, Website development and Learning/Teaching subjects related to Computer Science/Information Systems. IMO, C# is the best programming language and I love working with C# and other Microsoft Technologies.

  • Microsoft Certified Technology Specialist (MCTS): Web Applications Development with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Accessing Data with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Windows Communication Foundation Development with Microsoft .NET Framework 4

If you like my articles, please visit my website for more: www.rahulrajatsingh.com[^]

  • Microsoft MVP 2015

Comments and Discussions

 
QuestionThe Service Locator pattern increases coupling Pin
John Brett19-Jan-16 23:28
John Brett19-Jan-16 23:28 
AnswerRe: The Service Locator pattern increases coupling Pin
Rahul Rajat Singh20-Jan-16 0:04
professionalRahul Rajat Singh20-Jan-16 0:04 
With the service locator pattern, everything ends up dependent upon the service locator.

Which is why it's now considered to be a anti-pattern.

Still, it's interesting to discuss, on a theoretical basis, how a service locator is implemented.

BTW - I'd have used an IDictionary as the underlying storage, as you can use a Type as a key (i.e. you don't have to use the type's name).

Yes I agree to all your points. Typically what we do in our projects is:
  • If the actual concrete type is known then let the caller i.e. ApplicationFacade depend on an abstraction of the concrete type and inject the concrete type directly.
  • If the creation of concrete type is from a factory then let the caller depend on an abstraction of factory and inject the factory. The actual object will then be returned by the factory.
  • If the ownership is not supposed to go to the caller, let the caller depend on service locator abstraction, have a default service locator returning the default service and let the user define their own service locators that can be injected. Having an abstraction for service locator seems like an overkill here but it simply acts like a bridge so that the coupling could be reduced further.
And regarding the ServiceLocator implementation in the example, earlier I was planning to use the Type itself.
C#
public class ServiceLocator
{
    static Dictionary<Type, object> servicesDictionary = new Dictionary<Type, object>();

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

    public static T GetService < T >()
    {
        T instance = default(T);

        if(servicesDictionary.ContainsKey(typeof(T)) == true)
        {
            instance = (T) servicesDictionary[typeof(T)];
        }

        return instance;
    }
}

But then I switched to string to keep things simple for beginner's. But now thinking about it, it doesn't add that much of complexity as I initially thought it would for new comers.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.