0

In my job I work with C# (although the language is irrelevant for my question, and now I'd like to focus on Android) and we usually inject interfaces and not the actual classes, so I was wondering what are the real benefits of that?

As far as I know, I can understand interfaces (if no DI is taking part) as a way of not tying class methods to fixed implementations of their "dependencies", for example, we could decide to change the database engine so, using interfaces, the repository methods could be easily changed adjusting code to the new engine and the services accessing the repository would stay unaltered.

Given that, and please correct me if I'm wrong, I cannot see any real benefits of injecting interfaces. I mean, we already have DI so if we inject we are already decoupling and giving the possibility of altering the dependency as per our needs (injecting one implementation or another) then why injecting interfaces?

lennon310
  • 3,242

4 Answers4

1

Okay, so let's start from the world where you use class types as your injected dependency types.

public class MyDependency {}

public class MyService { public MyService(MyDependency myDependency) { //... } }

You're absolutely correct that this works just as well in terms of the real production code, assuming that that class adequately covers all the possible implementations of that dependency.

And even if multiple classes exist, you could still let those inherit from the same base class instead of the same interface, so you'd still be using class-based injection.

public class MyDependency {}
public class MyDependencyOne : MyDependency {}
public class MyDependencyTwo : MyDependency {}

public class MyService { public MyService(MyDependency myDependency) { //... } }

Production code wise, all good. But we also want to write tests. Specifically, when I call MyService.UploadUser(user), I want to make sure that MyService correctly combines the first and last name of the user and sends that as a single string to the dependency (which you can imagine to be a database repository).

Clearly, my unit test is going to use a real MyService object. But I don't actually want to upload real data to a real database, and that's what MyDependency precisely does in this scenario. Since the constructor for MyService demands a MyDependency, I can't avoid needing to supply a real MyDependency.

It would be a lot better if I could inject a fake MyDependency, sort of a secret agent that pretends to be the real thing but does not actually talk to the database and instead just tracks what it's being told.

But the MyService code should be written in a way that it works agnostic of getting a real/fake dependency. I'm referring to the type of the injected dependency here. So the question becomes: can we achieve this using a class type?

There's no shared logic between the real and fake dependency, so there's no benefit to inheritance. The only thing that the real and fake dependency share is that they have the same public interface (hint hint), their internals are completely different.

public interface IMyDependency 
{
    void SaveUserName(string fullName);
}

public class MyRealDependency : IMyDependency { /* same code as before */ }

public class MyFakeDependency : IMyDependency { // Snitch list which captures all values sent to the secret agent public List<string> CapturedValues { get; } = new();

public void SaveUserName(string fullName)
{
    this.CapturedValues.Add(fullName);
}

}

public class MyService { public MyService(MyDependency myDependency) { //... } }

When you look at MyFakeDependency, you can see that it collects and records anything that is sent to it. This means I can later check what was sent to it, and this is something I can use in my tests. For example:

public void Concatenates_first_and_last_name_of_user()
{
    var user = new User("Rick", "Sanchez");
    var fakeDependency = new MyFakeDependency();
    var service = new MyService(fakeDependency);
service.UploadUser(user);

// Confirm that MyService did call IMyDependency.SaveUserName
fakeDependency.CapturedValues.Should().HaveCount(1);

// Confirm that the sent name is first + last name
fakeDependency.CapturedValues[0].Should().Be(&quot;Rick Sanchez&quot;);

}

An interface is, in a very oversimplified way to put it, a base class which only allows you to define public signatures (methods and properties), but not implementations (method bodies).

Specifically in C#, you could also use an abstract class to the same effect as an interface here. However, C# does not allow multiple inheritance. A class can only inherit one abstract base class, but it can implement ("inherit") as many interfaces as it likes.
Because of this, in cases where you do not need any shared implementation logic (method bodies), an interface is a better choice over an abstract class.

Ask yourself how you would've done it using a class-type as the injected dependency. You'll find that you need to define an implementationless base class that covers both the fake and real dependency, and that's precisely what an interface is. Can you do it with a class on a technical level? Sure. But you're really just using it as a pseudo-interface.

Flater
  • 58,824
0

Typically we inject objects. But, well, interface injection is actually a thing.

One thing to understand, an interface is different than an interface (the keyword). This confusion is brought to you by the creators of Java who decided they wanted to support multiple inheritance after all and were to lazy/stuck-with-old-code to redesign how an abstract class worked. And so the interface was born.

So if what you're trying to ask is if you have to code against interface rather than a perfectly extendable class then no. You do not have to slap interfaces on everything. Unless you want multiple inheritance to work in those languages that need this hack.

Anyway, that's not an injection. That's what you're coding against.

This is interface injection:

// Service setter interface.
public interface ServiceSetter {
    public void setService(Service service);
}

// Client class public class Client implements ServiceSetter { // Internal reference to the service used by this client. private Service service;

// Set the service that this client is to use.
@Override
public void setService(Service service) {
    this.service = service;
}

}

// Injector class public class ServiceInjector { Set<ServiceSetter> clients; public void inject(ServiceSetter client) { clients.add(client); client.setService(new ServiceFoo()); } public void switchToBar() { for (Client client : clients) { client.setService(new ServiceBar()); } } }

// Service classes public class ServiceFoo implements Service {} public class ServiceBar implements Service {}

wikipedia.org - Dependency Injection - Interface Injection

What does that get you? Your dependencies have no idea what their clients are. Will you ever need that? I always value keeping things from knowing things that they don't need to know. Honestly though, I've never had good reason to do this. But it is called interface injection.

candied_orange
  • 119,268
0

There's the situation where a class implements an interface and nothing else. However, if you write software for MacOS / iOS, it is very, very common that you implement an interface in a quite unrelated class.

For example, if the user changes the contents of a textfield, you'd want to check if the change is allowed, you'd want to check if they hit the "return" key which might cause some action etc., and you write the code for this not in a textfield, but in a class which controls the textfield (and other fields). So an interface is declared by the TextField class, it is implemented by a quite unrelated class, and an instance of that class injected into a TextField instance to handle user interactions.

In the TextField class, you most definitely want an interface (for example because you have no idea whatsoever which class is going to implement it). And it's quite likely that you inject a concrete class instance. And that you have a dozen other classes that are injected into other TextField instances.

gnasher729
  • 49,096
0

The payoff comes when you start testing. Typically you create concrete classes with very limited functionality that implement the interface. For instance a dummy repository class may be coded to return a known values for it's getPasswordHash(userId) method. This allows testing the calling code without a backing database.

A second situation where an interface is highly desirable is when you write a library class that requires a callback method from the library user. It is easy to inject a dependency that implements your interface but much harder to have the caller supply a concrete class that extends your concrete class. Many OO languages do not support multiple inheritance, so don't use up the one chance at inheritance when there is a good alternative.

kiwiron
  • 2,384