C
C#2mo ago
Spiral Hero

Dependency Injection question

I was learning about coupling/decoupling and one thing that was confusing to me was this: Basically, people tell you to leave the instances for the calling of the constructor method, and instead just use an Interface that implements that class when using it on your code. The reason people will say this is useful includes (while not limited to) "Making so that if the signature of that class changes, the code using that class won't be affected, since it's using an abstraction of it, an Interface. Making it easier to change the class without breaking the code" While I understand that, doesn't this just changes the necessity of getting the signature correct from inside the code to "outside" the code? (And what I mean by that is calling the code, since you will need to pass the instance of this class anyways and if the signature has changed I don't see how it won't give errors the same exact way)
38 Replies
Omnissiah
Omnissiah2mo ago
the interface helps you understand what the class should really have public, what the class does to do this you need to study the problem and it helps also anticipate some of the problems you may encounter down the road
Spiral Hero
Spiral HeroOP2mo ago
But how does this process helps you avoid errors when changing the signature of a class?
Omnissiah
Omnissiah2mo ago
it helps you with that if you have more that one class that implements said interface the main point tho would be abstracting first to have less changes
Spiral Hero
Spiral HeroOP2mo ago
Not sure I quite understand yet 🤔
Omnissiah
Omnissiah2mo ago
i wouldn't say you have to have an interface for every class in your program if that's the doubt
Spiral Hero
Spiral HeroOP2mo ago
I just don't understand the practical benefit of doing this at all, since it just looks like to me that you are shifting the issue from the contents of the class to the calling of it instead.
Omnissiah
Omnissiah2mo ago
there are various reasons to have interfaces, one of those is dependencies you can for example have a project with the interfaces to your classes that you can use elsewhere without having to include all the concrete classes or for example you can share it with someone without giving them code that could be protected in some way
Spiral Hero
Spiral HeroOP2mo ago
I understand that interfaces are useful My question is more directed to this specific usage that does dependency injection with the use of Interfaces
Omnissiah
Omnissiah2mo ago
well this
Making so that if the signature of that class changes, the code using that class won't be affected
is not right, methods between interface and derived class must have (almost) exact corresponding signature almost in the sense that minor things like nullable differences for references are only a notice and not an error so if you change interface you have to update all the classes that implement it
Becquerel
Becquerel2mo ago
to give another perspective, i'm going to agree that i don't think this idea of 'if the signature of the class changes, the code using it won't be affected' is correct or even a reason i've ever heard for using interfaces, i think the point of interfaces is to be just the signature i would be interested in seeing any places you've seen which are expressing this viewpoint
Spiral Hero
Spiral HeroOP2mo ago
Maybe I misunderstood the class I was taking 🤔 what is the real reason for doing this DI with interfaces then?
Becquerel
Becquerel2mo ago
generally you use interfaces with DI for the purpose of testing. or more generally, swapping out implementations without the consuming code knowing you've done so i.e. if i have a class that takes in an IEmailService in my unit tests i can pass it a dummy IEmailService-implementer which doesn't actually send out emails and the class i'm testing doesn't know the difference
Spiral Hero
Spiral HeroOP2mo ago
Sorry, I'm still a noobie, having a hard time following. What do you mean the code knowing or not knowing, and what difference would that make?
Becquerel
Becquerel2mo ago
let me draft up a quick example for you
Spiral Hero
Spiral HeroOP2mo ago
Thank you so much 🙏
Becquerel
Becquerel2mo ago
public class SomeService
{
private readonly MoneySpender _spender;

public SomeService(MoneySpender spender)
{
_spender = spender;
}

public int DoImportantWork()
{
Console.WriteLine("Starting work...");
var result = _spender.DoSomethingExpensive();

return result + 4;
}
}

public class MoneySpender
{
public int DoSomethingExpensive()
{
// Pretend this method will cost you $50,000 every time it is called.
throw new NotImplementedException();
}
}
public class SomeService
{
private readonly MoneySpender _spender;

public SomeService(MoneySpender spender)
{
_spender = spender;
}

public int DoImportantWork()
{
Console.WriteLine("Starting work...");
var result = _spender.DoSomethingExpensive();

return result + 4;
}
}

public class MoneySpender
{
public int DoSomethingExpensive()
{
// Pretend this method will cost you $50,000 every time it is called.
throw new NotImplementedException();
}
}
i want you to think about how you'd test the SomeService class without going bankrupt you can't do it -- it has to use a MoneySpender, and the MoneySpender will always cost you 50k
Omnissiah
Omnissiah2mo ago
not only testing but potentially switching multiple versions of the code or even different implementations for example swap a client that uses serial port or ssh with just an interface for sending data or whatever other reason
Becquerel
Becquerel2mo ago
so you can't test SomeService
public class SomeService
{
private readonly IService _spender;

public SomeService(IService spender)
{
_spender = spender;
}

public int DoImportantWork()
{
Console.WriteLine("Starting work...");
var result = _spender.GetAnswer();

return result + 4;
}
}

public interface IService
{
int GetAnswer();
}

public class FakeService : IService
{
int GetAnswer()
{
return 12;
}
}

public class MoneySpender : IService
{
public int GetAnswer()
{
// Pretend this method will cost you $50,000 every time it is called.
throw new NotImplementedException();
}
}
public class SomeService
{
private readonly IService _spender;

public SomeService(IService spender)
{
_spender = spender;
}

public int DoImportantWork()
{
Console.WriteLine("Starting work...");
var result = _spender.GetAnswer();

return result + 4;
}
}

public interface IService
{
int GetAnswer();
}

public class FakeService : IService
{
int GetAnswer()
{
return 12;
}
}

public class MoneySpender : IService
{
public int GetAnswer()
{
// Pretend this method will cost you $50,000 every time it is called.
throw new NotImplementedException();
}
}
now consider this example you can give a FakeService to SomeService and it will work. SomeService doesn't know if you just gave it a FakeService or a MoneySpender. it just knows it's an IService this means you can now test SomeService without spending lots of money
Spiral Hero
Spiral HeroOP2mo ago
I understand that in the example whenever you run DoImportantWork you end up also running the DoSomethingExpensive. So in another example, when testing DoImportantWork, it would be hard to know if the issue isnt coming from DoSomethingExpesnive, since it is not run independentely if thats what you mean with the example But how would using an interface and passing the class via the constructor help?
Becquerel
Becquerel2mo ago
moreso what i mean is that there are (quite often) bits of code you want to run in normal usage, but not run in other cases. for example you don't want to actually send out emails to users when testing your app
Spiral Hero
Spiral HeroOP2mo ago
(i was typing and i didnt see the second message)
Becquerel
Becquerel2mo ago
interfaces let you section off awkward and annoying dependencies and step around them as needed
Spiral Hero
Spiral HeroOP2mo ago
(let me go back and read)
Becquerel
Becquerel2mo ago
ah, ok
Spiral Hero
Spiral HeroOP2mo ago
is fakeservice supposed to be an interface too? not a class?
Becquerel
Becquerel2mo ago
woops no, it's not, sorry
Spiral Hero
Spiral HeroOP2mo ago
Ah ok I think I'm starting to understand Let me think a bit more about it 😂 im kinda slow
Becquerel
Becquerel2mo ago
lol, no worries i think everyone had that experience with interfaces at first
Spiral Hero
Spiral HeroOP2mo ago
Ok so, the issue with coupling is that you cant use ClassA (in this case, SomeService) without running the exact implementation of ClassB While with DI (in this example via an Interface) you could try out other ClassB implementation for testing purposes Is that the takeaway?
Becquerel
Becquerel2mo ago
yes, that's a pretty good way to think of it though as MutableString noted, this is useful in cases besides testing you might, say, have an app that makes use of encryption, and you want to switch from one encryption algorithm to another... if you have an IEncryptionService interface which hides the details of what specific algorithm is used, you could swap it out for a different implementation and the rest of your codebase wouldn't know - wouldn't have to change
Spiral Hero
Spiral HeroOP2mo ago
Not knowing in this context means that it works without knowing the specific implementation correct?
Becquerel
Becquerel2mo ago
correct
Spiral Hero
Spiral HeroOP2mo ago
So you can change it at the calling and its not a issue ok i think i got it Thank you so much 🙏
Becquerel
Becquerel2mo ago
class A, speaking generally, shouldn't care about how class B does stuff. it should just care about what class B can do for it one model of 'good code' is having lots of little, closed-off, independent modules glad to hear it 🙂
Spiral Hero
Spiral HeroOP2mo ago
The only thing that is kinda weird to me still is that it sounds a little bit more niche than what I expected from how much people say this is necessary but maybe im just very wrong
Omnissiah
Omnissiah2mo ago
i would like to add that an interface represent a way to use a class/concept (also said contract) so a class can obviously implement many interfaces (look for example at primitives like int) which can matter in different contexts it's not niche, it's just useful when you have to organize hundreds of classes or more
Becquerel
Becquerel2mo ago
it's one of the many patterns that only becomes truly useful on 'serious' projects, i.e. ones with lots of classes and enough complexity that testing is a requirement i will also say that you don't have to use interfaces everywhere. in fact i actually prefer not using interfaces if i can because i want my tests to exercise as much 'real code' as they can but there's some stuff, like database calls, http calls, etc., that you really just have to mock out sometimes
Spiral Hero
Spiral HeroOP2mo ago
Got it. Ill try to use it in some classes that I feel like could have different implementations at the very least
Want results from more Discord servers?
Add your server