DI - One Interface - Many Implementations
DI - One Interface - Many Implementations

.NET – Dependency Injection, One Interface and Multiple Implementations

In programming world, it is very common to have multiple implementations of the same interface.

Most commonly used pattern is Factory pattern and it is also based on designing an interface and having multiple implementations of that interface. Then Factory Pattern uses a manager class to decide which implementation should be called. This manager class may use a simple Switch statement (or switch expression) or if it is a dynamic factory, then it may reflection in some cases (Activator.CreateInstance).

This whole pattern is very straight forward to implement when you do not have a dependency injection container. But many applications have been using the dependency injection for injecting every dependency that is needed by each class in the system. While this kind of approach is still questionable and some people argue that this may mean that you are abusing the dependency injection (and in some cases , I agree with this argument), that argument is not the topic of discussion in this article.

In this article, we are going to see what are various options available for us, if we have one interface, multiple implementations and we use dependency injection container. I have used some suggestions provided in StackOverflow question and tried to compile some of my thoughts around those suggestions.

The Problem Statement

Let’s consider a simple problem statement which we will use for the discussion.

Let’s say there is a hypothetical application which has a reminder service. There can be three types of reminders – Email Reminders, SMS Reminders and Push Notifications.

Let’s say we have three controllers and they need three different implementations of the reminder service:

  • FirstController, which needs EmailReminderService implementation
  • SecondController, which needs SmsReminderService implementation
  • ThirdController, which needs PushNotificationReminderService implementation

So, we have a single interface and there are three different implementations as shown in the below screenshot.

How to register multiple implementations of single interface ?

Now that we have our interfaces ready, our next question is – how are we going to register the multiple implementations of a single interface?

For the sake of this discussion, let’s say we are using the default dependency injection container provided by .NET.

So, if you are familiar with the default .NET DI Container, registering dependencies is an easy task. The below code snippet shows how the reminder service interface and implementations have been registered.

How to resolve correct implementation ?

The next big question is – how are we going to resolve correct implementation now ?

This is tricky. When a single interface has single implementation, it is easy. We need to register it in DI and then use it in the constructor as parameter and that’s it. But in this particular case, where single interface has multiple implementations, it is not that straight forward. But it is not impossible too.

There are various approaches that we can take. Let’s try to discuss three approaches that I can think of right now.

Option 1 – Inject IServiceProvider

One option is to directly inject the IServiceProvider interface to all the three controllers. Then each controller can resolve the type they want by using GetService or GetServices call.

In our case, as single interface has multiple implementations, we need to use GetServices call, which would return all the registered implementations. Then we can search for concrete type by using Linq over the collection as shown in the below code snippet.

This is the simplistic solution to resolve any type that we need. It also has its disadvantages. This pattern is called as Service Locator pattern and many people want to stay away from this, because widely, it is considered to be an anti-pattern.

The disadvantage of using this pattern is – your code becomes hard to test. You will have to take a lot of efforts to mock the dependencies. Also, using this pattern may result in more run-time errors, than compile time errors. The constructor only needs IServiceProvider and hence it is very easy to overlook all the dependencies needed.

Although it is widely considered to be an anti-pattern, it also has its own advantages. One of the major advantage is – run-time linking. At compile time, the dependencies may not be known in some cases, and that’s when this pattern may be very useful. So, there is complete “de-coupling” between dependencies and the type.

So, if you want to use this pattern, you should be aware of what are needs, what are advantages / disadvantages of this option so that you can take appropriate measures to avoid / minimize impacts due to cons of this pattern.

Option 2 – Inject multiple and then select one

This is an improvement over the previous option. So, instead of giving all the power to resolve any type registered in DI, what we can do is – we can inject IEnumerable<IReminderService> in the constructor as shown below. This will ensure that all the implementations of IReminderService will get injected into the controller. Then we can use Linq over this collection to get the appropriate type.

The advantage here is – the service provider class is not injected, so this controller cannot accidentally resolve the types it does not need.

Like previous approach, the disadvantage of this approach is testability. Your controller code knows the type name. Hence, if you want to inject any mocks instead of actual implementation, it would not be possible.

Option 3 – Service Delegate

Another option is to have a method resolve appropriate dependency, based on some kind of key / token.

In this approach, the method should take a token as input parameter and then based on that token, the appropriate reminder service instance should be resolved and returned to the caller.

For this, we can define a delegate – let’s say ReminderServiceResolver. This delegate then can be registered as a dependency as shown in the code snippet given below. The corresponding method basically takes a token as parameter and then it returns corresponding service.

This approach has advantage that the type resolution is part of delegate and it is not part of the actual class. This design helps in testing. For testing, you will need to setup the delegate which will return the mock or stubs.

Option 4 – Factory

This is the last option that I am going to cover in this article. This is traditional approach that most of us already may have used very often. This is kind of extension of previous approach (Or we can say that previous approach is kind of derived from this approach.)

Here, a separate – ReminderServiceFactory – class and its corresponding interface can be created. Then this interface should be injected in the controllers. Controllers can then pass appropriate tokens to the factory method, to get appropriate reminder service instance.

The advantage that I see here is – there is no magic. Everything is clear and consistent with rest of the application. Every dependency is known at the compile time. Also, stubbing and mocking is easy.

Bonus Option – Individual Interfaces Deriving from Single Interface

This is a bonus tip. You can create additional interfaces from IReminderService, one corresponding to each concrete implementation. So, you have one interface IEmailReminderService, derived from IReminderService and its corresponding implementation EmailReminderService. Similarly, other individual interfaces can be defined and class definitions should be adjusted to use individual interfaces

The complete code is shown in the code snippet given below.

The disadvantage here is additional empty interfaces that you need to create. Many of us may not like it.

But the advantage is – registering and resolving dependencies is consistent with rest of the application. Also, there is no need of additional factory method to resolve the appropriate reminder service.

Wrapping Up

We discussed only a specific dependency injection container – the default .NET DI container. In this discussion, I have tried to use the default DI container from .NET and without adding any additional NuGet package. The ideas that I have mentioned here may not be usable as it is, but I am sure this article will provide you some ideas about how can you solve the problem at hand.

Of course, these are not the only options available. There can be additional ways if you are using other dependency injection containers. Do you have some additional ideas ? I would love to hear from you about the approaches that you have used / planning to use. Let me know your thoughts.

Leave a Reply to Suman ChakrabartiCancel reply

This Post Has 11 Comments

  1. geri

    Good article. Instead of string token you might want to introduce Enum as a param input, in order to avoid mistyping.

  2. Suman Chakrabarti – Redmond, WA
    Suman Chakrabarti

    I usually add a method called ShouldProcess for these classes and run them all in a loop or set an enum for certain circumstances.

  3. Ocnah

    I usually create separate interfaces for the different implementations (the bonus option). I don’t see anything wrong with that.

    1. Manoj Choudhari
      Manoj Choudhari

      Thanks for sharing your thoughts ! It is indeed better to have separate interfaces whenever possible. Always remember I from SOLID while designing the interfaces. 😉

  4. José G Rivera (Gabo)
    Gabo

    What about creating generics interfaces like IReminderService passing the class implementation in the DI.
    services.AddScoped<IReminderService, SmsReminderService>()

    Then in the constructor of the controller you’ll have FirstController(IReminderService reminderService).

    I’ve been using this approach for a long time and it gives me the flexibility of not having to create empty interfaces like to bonus tip.

  5. Sam

    I dont know why we cant simply name each one like other DI frameworks do so you can the do something like this in constructor ([name=”sms”] IReminderServices) and that will return sms implementation

  6. alberto

    Hi, great article! One question: in the solution #3, is there a specific reason why services are registered as Scoped and Factory as Transient?

    1. Manoj Choudhari
      Manoj Choudhari

      There is no specific reason as such. In fact the factory can be singleton as it does not hold its own state and objects created by it are anyway destroyed once the scope is done. These are my thoughts of course. I would like to know your opinion as well. Thanks.

  7. Vivek KR

    Can we implement this same with the singleton instance.?

    1. Manoj Choudhari
      Manoj Choudhari

      I hope you are asking if you have 3 implementations of an interface and you need to register all of these implementations as singleton,. If this is your question, then I would say it is possible.