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
SecondController, which needs
ThirdController, which needs
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
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.
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.