4

Now first I want to mention that I am not quite sure what is the term describing the issue at hand but I hope that I can illustrate it with some code.

Problem: A shared class library defines an Interface and each Application referencing this library provides its own implementation of that interface. Now inside the shared library, I want to call the actual implementation of that method.

Trying to solve this problem I imagined a simple scenario where 2 platform specific applications Windows, Android share some code except for some platform-specific functions that need to be implemented separately for each system. At runtime, I need to transparently call that implementation knowing that it's already provided by the implementer.

Three Console Applications are created Shared, Windows, Android

Shared Project defines an Interface IShared

namespace Project
{
    public interface IShared
    {
        void Speak(string word);
    }
}

Android Implements IShared

namespace Project.Android
{
    public class SpeakAndroid: IShared
    {
        public void Speak(string word)
        {
            Console.WriteLine("Android Speaks");
        }
    }
}

Windows Implements IShared

namespace Project.Windows
{
    public class SpeakWindows: IShared
    {
        public void Speak(string word)
        {
            Console.WriteLine("Speak Windows");
        }
    }
}

Now in order for the Shared project to be able to call each platform specific Speak function, I decided to declare a

public static IShared SharedDefinition { get; set; }

to which I assign a new Implementation inside Windows and Android projects like so

Windows:

Project.Program.SharedDefinition = new SpeakWindows();

Anrdoid:

Project.Program.SharedDefinition = new SpeakAndroid();

This way in the Shared project I can call Program.SharedDefinition.Speak(); and depending on the running platform i will get different Implementations of Speak.

Question(s):

  • Is this a correct way of solving the above problem? does Service Locator Pattern has anything to do with this?
  • For further reading, does the problem above have a name or pattern that best deals with it?
3
  • 1
    get rid of 'static' and its fine
    – Ewan
    Commented Sep 2, 2018 at 7:06
  • @Ewan Kindly, Can you further elaborate why everyone sees static as a drawback? Commented Sep 2, 2018 at 12:30
  • see my answer below
    – Ewan
    Commented Sep 2, 2018 at 13:28

3 Answers 3

4

In short

This is a valid way to solve the abstraction problem: it makes sure that your library doesn't depend on concrete implementations.

The general term of this technique is called dependency inversion. But instead of injecting the dependency into the objects or functions that need it, you use a global intermediary. Such intermediary is a service locator.

More details: aren't service locators more complex ?

Service locators usually encapsulate access to several different kind of services services, and for each several services. The scenario is then more complex than in your case:

  • SL gets initialized. In principle it's a singleton
  • Services are then registered to the SL (optionally with some attributes to find them back)
  • Client then gets the service from the SL (optionally with some lookup for choosing the most suitable one)

But in your case, the locator is simplified to the extreme, as you expose just a single service:

  • SL is just a global static object.
  • Single IShared service to be used by the library is registered by using-context via the setter of the global static object.
  • Library accesses the configured service via the getter of the global object.

In this article you'll find another example of service locator between these two extremes. (I mention it here more because of the very clear distinction it makes between dependency inversion, dependency injection and inversion of control)

More details: is this a good idea ?

The main inconvenience of the service locator is that all your library classes may depend on it, perhaps creating a higher coupling than necessary inside the library.

You may also lose some flexibility because you could not use different implementations of the abstraction for different objects. May be you don't need it here, but in other scenario this pattern could be counterproductive.

2

re: Why is static bad?

So you have nicely abstracted your 'ISpeaker' (lets not called it shared) but then put it in a static Program.Speaker.

Now if you have another class in your app, say LoginController which wants to use the speaker it will have..

public class LoginController 
{
    public void Login() 
    {
        Program.Speaker.Speak("you have logged in!");
    }
}

Although your concrete Speaker classes are not coupled to the app anymore your LoginController is needlessly coupled to your Program.

A better alternative would be:

public class LoginController 
{
    public LoginController(ISpeaker speaker)
    {
        this.speaker = speaker;
    }
    public void Login() 
    {
        this.speaker.Speak("you have logged in!");
    }
}

Now you code is even less coupled, with all the benefits that brings.

re: Why is a Service Locator bad?

A service locator would be an object which returned a concrete instance of ISpeaker on request. eg

public class LoginController 
{
    public LoginController(ILocator locator)
    {
        this.locator= locator;
    }
    public void Login() 
    {
        var speaker = this.locator.Get<ISpeaker>();
        speaker.Speak("you have logged in!");
    }
}

This gets around the tightly coupled static field from the first example, but can cause a runtime error if the locator isn't correctly setup with an ISpeaker.

This is particularly bad because without looking inside the LoginController code, you don't know that it needs an ISpeaker. Whereas requiring an ISpeaker explicitly as a construction parameter forces calling code to pass something in.

re: Does the problem have a name?

The solution discussed is called Dependency Injection. I'm not sure that the problem of sharing code across two applications has a name.

9
  • While your remarks are wise and I could in general agree, they might not necessarily be appropriate here: OP refers to a platform dependent service, where at run time there should be only one single choice. So there is no reason that program would call the android service but login would call the windows service (so no objective pb for the static in this case). If library has hundreds of related classes all depending on a single platform service, then using DI would unnecessarily bloat API and intra-library calls. Finally, yes the locator is an anti-pattern; but init problems can be catched.
    – Christophe
    Commented Sep 2, 2018 at 13:59
  • @Christophe there are several scenarios where you may need to inject the service rather than referencing a static in Project. for example have you thought about which library Project exists in? But why even talk about them when there is testing to think of.
    – Ewan
    Commented Sep 2, 2018 at 14:04
  • @Erwan as already said, I fully agree with your position in the general case: DI is more flexible than the SL because you could chose at runtime different implementations for different purposes in the same program, and even change dynamically. Of course, projects using the library could be very creative. And maybe the library could be less monolithic. But if it's really only about a platform specific service as suggested by OP (so more a configuration issue than a dynamic behavior), then I really do not see an issue.
    – Christophe
    Commented Sep 2, 2018 at 14:16
  • @Ewan Now you code is even less coupled, with all the benefits that brings. I can't see the point of just delaying the inevitable with DI, I mean yeah now the LoginController isn't tightly coupled anymore but somewhere down the road another class must end up tightly coupled anyway. Been reading a lot about this topic and the more I see examples the more it gets way worse. Commented Sep 2, 2018 at 14:38
  • @AhmedTarek There is not reason you can't avoid tight coupling entirely. The oddity with your question is that having created the interface to avoid one instance of coupling you then immediately couple something else. Do you intend to write unit tests? You'll find it hard with the static. Is it a xamarin project? You'll have a tonne of these things and statics will be a pain. Don't cut corners
    – Ewan
    Commented Sep 2, 2018 at 14:54
1

As far as I can see from this link, Service Locator means that there is another class ShardProvider, which provodes all other code the correct implementation of the IShared. It is meant to be instantiated as Singleton.

Essentially this has same up- and downsides as using Sigleton pattern, it just have moved the responsibility of instantiating IShared from the IShard itself to a separated class.

In your code, if your SharedDifinition property is defined in some utility class Settings, then it looks like Service Locator, with difference:

  • it seems to be writable to anything, which is dangerous. The setter should rather be internal at least.
  • the Service Locator pattern would rather prescribe you to have the property non-static, instead making Setting a singleton

If the SharedDifinition property is in your problem-specific class which actually uses it, it seem to have stretched quite far from Service Locator or Singleton. Note that user of the problem-specific code would have to always spend their attention to remember where this code takes the IShared from, while still getting disadvantages of Singleton/Service Locator, because the property is static.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.