0

Okay, first of all I understand the concept of IoC container! It's used to implement automatic dependency injection so you won't have to manually inject dependencies to class. It can automatically detect that if a class requires, let say for example IEmail dependency, inject Gmail class into it.

I noticed that, IoC containers are good for 1:1 interface and class. What I mean is, it is good if you have an interface and you only have one class to implement it. For example, IEmail will only be injected with Gmail class always. But when you add Outlook class, Yahoo class, etc., correct me if I'm wrong, but I don't think IoC container will handle it easily.

I have been searching for the answer if do we need to use an interface if only one class will implement it, majority, if not all of the answers are "No, you should not!". My question is, doesn't IoC container contradict the purpose of interface since it's only good for "interface to be implemented by one class always"?

5
  • 6
    In many cases, the "second implementation" of an interface is the mock you inject when unit testing. Aug 29, 2021 at 14:46
  • @PhilipKendall actually that's the only thing I can think of the use of IoC container. I'd like to implement it, but I am discouraged by the answers I'm reading that you should never create an interface for one class.
    – Jhe
    Aug 29, 2021 at 14:50
  • It's not quite true that you shouldn't create interfaces if only one class implements it, it's just that you should be more judicious about it - people sometimes go and split nearly every nontrivial component into an interface-implementation pair, following some prescribed template, or "architecture". However, the problem is that these interfaces are based on assumptions that were made at the very start, when people knew the least about the project - so they don't hold up later on, and become a nuisance. Use interfaces to strategically isolate yourself from what you find to be volatile details Aug 29, 2021 at 15:30
  • You can move configuration out of the code. Aug 29, 2021 at 19:11
  • I think DI containers are only helpful if you have only one implementation per interface. Interfaces are much more interesting if you have multiple implementations per interface. This is why I think DI containers are a bad idea. I write about this six years ago here: criticalsoftwareblog.com/2015/08/23/… Oct 11, 2021 at 15:04

6 Answers 6

6

IoC container advertise themselves with code snippets like:

container.Register<IEmail,Gmail>()

which suggest you always need an interface and that its a 1 to 1 relationship. But this is not true. The following methods can also be used:

container.Register<Gmail>() //no interface
container.Register<IEmail, Gmail>(name = "gmail") // inject into classes that need gmail
container.Register<IEmail, Outlook>(name = "outlook") // inject into classes that need outlook
container.Register<IEmail>(fac => { if(DateTime.Now > Midnight) return new Gmail()}) //inject a factory that creates a instance

And many more

1
  • 1
    So, it ok to feed containers after midnight? Sep 1, 2021 at 16:53
3

Not necessarily.

Yes, most IOC containers want a one-to-one mapping of interface to implementation. But that is per program. Different consumers of a library will likely have their one implementation of the dependency that the library needs.

Realistically, you should look at it the other way: why do you need a magical dependency injector if the dependencies never vary? You could hard code them and eliminate piles of complexity.

2

There are a few reasons to utilize an interface over instantiating the class directly. Specifically loose coupling, abstraction, and lifecycle management.

Around loose coupling, if you are going to write a unit test for your main class which wants to send a notification, you likely don't want to actually send an email in a unit test (in other types of tests you may). Using an interface allows you to mock or fake the implementation so that you aren't calling your email provider directly.

For abstraction, I wouldn't want my core business logic to know about or care about the email provider (or even what type of notification is being sent). Also, utilizing Dependency injection can clean up/simplify injecting configs and credentials into the email class itself.

On the lifecycle, depending on the purpose of the class, you may want a different lifecycle than the calling class. For instance, maybe you want your email provider to be a singleton, vs the calling class is transient. I'm not arguing what the correct lifecycle is, but you will find times that it's expensive to 'new up' classes vs reusing them.

2

..., what is the purpose of IoC container?

The purpose of an IoC container is technically unrelated to the existence of interfaces. You could have a functional though barebones IoC container that only uses concrete types, and thus relies on inheritance for its type variation. It's not great, but it can get the basic job done.

If it is a bad practice to use an interface if only one class will implement it, ...

I have been searching for the answer if do we need to use an interface if only one class will implement it, majority, if not all of the answers are "No, you should not!".

The interface advice you've been given is... imperfect.

It is factually correct that if there is definitively never ore than one implementation, an interface isn't being leveraged for its full potential.
However, there are some major caveats to claiming that you know that there is definitively no other possible implementation for this interface. There are so many caveats, that in my experience as a developer I now default to disbelieving any claim by any developer that things will definitely never change. Almost always, such a claim is a matter of developer naiveté/arrogance/obliviousness/oversimplification.

  • Well-developed test suites tend to inherently fill that need by creating a mocked version of the interface.

With proper code coverage, pretty much all components in your software will be tested, and therefore its dependencies will be mocked. If your interface is being used as a dependency by a class, then that class' unit tests will be mocking your interface, which means that you now have a second implementation of that interface (the real implementation + the mocked one).

  • You cannot tell the future. Requirements change, or may have been misinterpreted, or your codebase may become ripe for an upgrade due to its popularity and longevity.

This is in bold because it is by far the biggest flaw I see developers make. You assume that what is true today will be true for eternity, which is simply not the case. Either because you might be wrong today, or because tomorrow will be different from today.

While there may be only one implementation today, nothing is stopping the future from coming with new requirements that add additional variations into the mix.

For example, my software might use DateTime.Now for all its clock-based needs today. But maybe tomorrow there's a better clock-based library, and I'd want to use that. If I abstract DateTime.Now into an IClock based library today, even though it's the only implementation I currently think I'll use, I will reap the benefits tomorrow when I can simply write a new IClock implementation for the better library.

Without that IClock interface, I'm stuck having to manually rewrite every reference or dependency on DateTime.Now in my codebase. At best, it's an annoying menial task. At worst, if I forget a few, that's going to be the source of some majorly annoying bugs.

With that IClock interface, I simply have to write a new class which implements that IClock interface using the fancy new library I wrote, and I have to change 1 line in my IOC container registration where it says <IClock,DateTimeClock> to <IClock,NewLibraryClock>.
Note also that this work did not require me to figure out what my old DateTime-related code did, how it worked, nor did I need to change any of that code and risk breaking it. I was able to simply write a new thing and plug it in. No fuss, no muss.

For example, IEmail will only be injected with Gmail class always. But when you add Outlook class, Yahoo class, etc., correct me if I'm wrong, but I don't think IoC container will handle it easily.

You're conflating two different uses for interfaces.

For the IoC example, we're talking about how dependencies can change over time. For example, your application uses Gmail servers to send out its notification today, but tomorrow Microsoft drops their prices and you want to start sending out your email notifications using Outlook servers. This is the kind of scenario where you'd be changing your IoC registration from <IEmailSender, GmailSender> to <IEmailSender, OutlookSender>.

But what you're talking about here is runtime variability, i.e. you want your application to use Gmail or Outlook dynamically, based on e.g. the user's email address. You're right that no IoC container will handle this kind of choose-your-email-implementation easily, because this is not the kind of scenario that an IoC container is designed for.

IoC containers work with global registrations, i.e. one definitive concrete type assigned to a more abstracted type (interface). When you have multiple concrete types that you want to use, you'll find that your IoC container is not great at choosing between different concrete types based on some arbitrary logic. It's simply not what an IoC container was designed to do.

This second scenario is much more suited to a factory which decides which IEmail implementation to use. This requires some business logic, which is something that your IoC container can provide out of the box.
When you get to this stage, you create the factory, and it is the factory which gets registered to the IoC container. This is a very different scenario from the one your question is focusing on.

Note: there are ways to force this factory logic into an IoC container directly, but for any non-trivial example it's in my opinion an allround bad idea to do so and much better implemented using an explicitly designed factory class.

0
  1. The opinion that one interface & one implementing class would be bad style, is wrong.

    Assume you do MVC, (Data) Model/View/Controller. You'll start writing a controller class, normally the entry point. It creates a data model and view. Especially the view can have the controller passed or injected. The controller is in control, but the view does not necessarily need access to all (public) methods of the controller. Instead of passing the entire controller class, one may pass an extracted interface.

    The counter-argument that such controller class then probably is large, maybe serves several views, and hence several interfaces might actually comprise the controller class, is true. But! It always can make sense to extract an interface from a class, instead of implementing an interface to one or more classes.

  2. IoC container / dependency injection indeed often let the code program against a bean of some interface. As you mentioned, this allows chosing an other implementation. Note: some containers allow a class.

    However there is a very important point: programming against interfaces is the better style:

     List<String> names = new ArrayList<>();
     Set<String> family(List<String> fullNames);
    

    Without doubt, the above usage of interfaces - also with a single implementing class -, is most generic/flexible and most expressive.

    There is even more: the class and interface can belong to a third party library with different releases, the class having more release versions.

  3. Several beans with same interface and different implementations is often possible, by using different bean names.

0

The premise is nonsense. There are basically two reasons to use an interface:

  • define a specific, indivisible piece of behavior and use it as a contract
  • decoupling

Neither has a requirement regarding the number of implementations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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