39

I have an ASP.NET Core application. The application has few helper classes that does some work. Each class has different signature method. I see lot of .net core examples online that create interface for each class and then register types with DI framework. For example

 public interface IStorage
 {
    Task Download(string file);
 }

 public class Storage
 {
    public Task Download(string file)
    {
    }
 }

 public interface IOcr
 {
     Task Process();
 }

 public class Ocr:IOcr
 {
    public Task Process()
    {

    }
 }

Basically for each interface there is only one class. Then i register these types with DI as

 services.AddScoped<IStorage, Storage>();
 services.AddScoped<IOcr,Ocr>();

But i can register type without having interfaces so interfaces here look redundant. eg

 services.AddScoped<Storage>();
 services.AddScoped<Ocr>();

So do i really need interfaces?

Nate Barbettini
  • 43,095
  • 21
  • 121
  • 135
LP13
  • 20,711
  • 38
  • 136
  • 286

4 Answers4

47

No, you don't need interfaces for dependency injection. But dependency injection is much more useful with them!

As you noticed, you can register concrete types with the service collection and ASP.NET Core will inject them into your classes without problems. The benefit you get by injecting them over simply creating instances with new Storage() is service lifetime management (transient vs. scoped vs. singleton).

That's useful, but only part of the power of using DI. As @DavidG pointed out, the big reason why interfaces are so often paired with DI is because of testing. Making your consumer classes depend on interfaces (abstractions) instead of other concrete classes makes them much easier to test.

For example, you could create a MockStorage that implements IStorage for use during testing, and your consumer class shouldn't be able to tell the difference. Or, you can use a mocking framework to easily create a mocked IStorage on the fly. Doing the same thing with concrete classes is much harder. Interfaces make it easy to replace implementations without changing the abstraction.

Nate Barbettini
  • 43,095
  • 21
  • 121
  • 135
  • Great answer. And in addition to the benefits of interfaces for testing, I would like to add that interfaces also allow for interception of dependencies, for instance using the Decorator design pattern. Additional behavior, for instance, can be added to `IStorage` without having to change the `Storage` implementation itself. For instance, you might want to record every downloaded file with their size, or prevent certain users from downloading certain files. – Steven Sep 22 '20 at 07:56
14

Does it work? Yes. Should you do it? No.

Dependency Injection is a tool for the principle of Dependency Inversion : https://en.wikipedia.org/wiki/Dependency_inversion_principle

Or as it's described in SOLID

one should “depend upon abstractions, [not] concretions."

You can just inject concrete classes all over the place and it will work. But it's not what DI was designed to achieve.

MindingData
  • 9,939
  • 5
  • 38
  • 56
  • 1
    But if you're using some 'component' someone else has created (without interfaces) you may have no choice right? – Simon_Weaver Dec 28 '18 at 03:53
  • 2
    In this situation you could create an interface for that component. Should be one ctrl + . away – NicoBerrogorry Dec 13 '19 at 17:27
  • 1
    Dependency Injection is not a tool for Dependency Inversion. The two concepts often go hand in hand (and rightly so) but you can accomplish either without the other. – Snail Cadet Sep 09 '20 at 19:12
2

I won't try to cover what others have already mentioned, using interfaces with DI will often be the best option. But it's worth mentioning that using object inheritance at times may provide another useful option. So for example:

public class Storage
 {
    public virtual Task Download(string file)
    {
    }
 }


public class DiskStorage: Storage
 {
    public override Task Download(string file)
    {
    }
 }

and registering it like so:

services.AddScoped<Storage, DiskStorage>();
RonC
  • 23,270
  • 13
  • 67
  • 104
  • But it's only useful with Abstract classes, right? Your Storage class isn't decorated with `abstract` keyword. It contains virtual method - virtual ones must have default implementation as opposed to abstract methods. – Alex Herman Apr 08 '19 at 17:00
  • Nope, good question though. You _could_ make Storage an Abstract class and in which case you wouldn't need to provide an implementation of `Download` in that class. But Storage can be a regular ol' class as I illustrated and it can use virtual for method that can be overridden as shown. Declaring a method as virtual indicates to the compiler that a derived class is allowed to provide it's own implementation of the method. You can learn more here: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/virtual – RonC Apr 08 '19 at 17:08
2

No, we don't need interfaces. In addition to injecting classes or interfaces you can also inject delegates. It's comparable to injecting an interface with one method.

Example:

public delegate int DoMathFunction(int value1, int value2);

public class DependsOnMathFunction
{
    private readonly DoMathFunction _doMath;

    public DependsOnAFunction(DoMathFunction doMath)
    {
        _doMath = doMath;
    }

    public int DoSomethingWithNumbers(int number1, int number2)
    {
        return _doMath(number1, number2);
    }
}

You could do it without declaring a delegate, just injecting a Func<Something, Whatever> and that will also work. I'd lean toward the delegate because it's easier to set up DI. You might have two delegates with the same signature that serve unrelated purposes.

One benefit to this is that it steers the code toward interface segregation. Someone might be tempted to add a method to an interface (and its implementation) because it's already getting injected somewhere so it's convenient.

That means

  • The interface and implementation gain responsibility they possibly shouldn't have just because it's convenient for someone in the moment.
  • The class that depends on the interface can also grow in its responsibility but it's harder to identify because the number of its dependencies hasn't grown.
  • Other classes end up depending on the bloated, less-segregated interface.

I've seen cases where a single dependency eventually grows into what should really be two or three entirely separate classes, all because it was convenient to add to an existing interface and class instead of injecting something new. That in turn helped some classes on their way to becoming 2,500 lines long.

You can't prevent someone doing what they shouldn't. You can't stop someone from just making a class depend on 10 different delegates. But it can set a pattern that guides future growth in the right direction and provides some resistance to growing interfaces and classes out control.

(This doesn't mean don't use interfaces. It means that you have options.)

Scott Hannen
  • 21,450
  • 3
  • 33
  • 44