Why should I use dependency injection?
up vote
72
down vote
favorite
I am having a hard time looking for resources on why I should use dependency injection. Most of the resources that I see explains that it just passes an instance of an object to another instance of an object, but why? Is this just for cleaner architecture/code or does this affect performance as a whole?
Why should I do the following?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
Instead of the following?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
architecture object-oriented-design clean-code
|
show 14 more comments
up vote
72
down vote
favorite
I am having a hard time looking for resources on why I should use dependency injection. Most of the resources that I see explains that it just passes an instance of an object to another instance of an object, but why? Is this just for cleaner architecture/code or does this affect performance as a whole?
Why should I do the following?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
Instead of the following?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
architecture object-oriented-design clean-code
8
You are introducing a hard-coded dependency to deactivateProfile() (which is bad). You have more decoupled code in the first one, which makes it easier to change and to test.
– Aulis Ronkainen
2 days ago
3
Why would you do the first one? You're passing in a Setting and then ignoring its value.
– Phil N DeBlanc
2 days ago
42
I don't agree with the downvotes. While the subject matter may be considered trivial to experts, the question has merit: if dependency inversion should be used, then there should be a justification for using it.
– Flater
2 days ago
10
@PhilNDeBlanc: This code is clearly oversimplified and not really indicative of real world logic. However,deactivateProfile
suggests to me that setting theisActive
to false without caring about its previous state is the correct approach here. Calling the method inherently means that you mean to set it as inactive, not get its current (in)active status.
– Flater
2 days ago
2
Your code is not an example of dependency injection or inversion. It's an example of parameterization (which is often much better than DI).
– jpmc26
2 days ago
|
show 14 more comments
up vote
72
down vote
favorite
up vote
72
down vote
favorite
I am having a hard time looking for resources on why I should use dependency injection. Most of the resources that I see explains that it just passes an instance of an object to another instance of an object, but why? Is this just for cleaner architecture/code or does this affect performance as a whole?
Why should I do the following?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
Instead of the following?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
architecture object-oriented-design clean-code
I am having a hard time looking for resources on why I should use dependency injection. Most of the resources that I see explains that it just passes an instance of an object to another instance of an object, but why? Is this just for cleaner architecture/code or does this affect performance as a whole?
Why should I do the following?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
Instead of the following?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
architecture object-oriented-design clean-code
architecture object-oriented-design clean-code
edited 12 hours ago
Peter Mortensen
1,11621114
1,11621114
asked 2 days ago
Daniel
500124
500124
8
You are introducing a hard-coded dependency to deactivateProfile() (which is bad). You have more decoupled code in the first one, which makes it easier to change and to test.
– Aulis Ronkainen
2 days ago
3
Why would you do the first one? You're passing in a Setting and then ignoring its value.
– Phil N DeBlanc
2 days ago
42
I don't agree with the downvotes. While the subject matter may be considered trivial to experts, the question has merit: if dependency inversion should be used, then there should be a justification for using it.
– Flater
2 days ago
10
@PhilNDeBlanc: This code is clearly oversimplified and not really indicative of real world logic. However,deactivateProfile
suggests to me that setting theisActive
to false without caring about its previous state is the correct approach here. Calling the method inherently means that you mean to set it as inactive, not get its current (in)active status.
– Flater
2 days ago
2
Your code is not an example of dependency injection or inversion. It's an example of parameterization (which is often much better than DI).
– jpmc26
2 days ago
|
show 14 more comments
8
You are introducing a hard-coded dependency to deactivateProfile() (which is bad). You have more decoupled code in the first one, which makes it easier to change and to test.
– Aulis Ronkainen
2 days ago
3
Why would you do the first one? You're passing in a Setting and then ignoring its value.
– Phil N DeBlanc
2 days ago
42
I don't agree with the downvotes. While the subject matter may be considered trivial to experts, the question has merit: if dependency inversion should be used, then there should be a justification for using it.
– Flater
2 days ago
10
@PhilNDeBlanc: This code is clearly oversimplified and not really indicative of real world logic. However,deactivateProfile
suggests to me that setting theisActive
to false without caring about its previous state is the correct approach here. Calling the method inherently means that you mean to set it as inactive, not get its current (in)active status.
– Flater
2 days ago
2
Your code is not an example of dependency injection or inversion. It's an example of parameterization (which is often much better than DI).
– jpmc26
2 days ago
8
8
You are introducing a hard-coded dependency to deactivateProfile() (which is bad). You have more decoupled code in the first one, which makes it easier to change and to test.
– Aulis Ronkainen
2 days ago
You are introducing a hard-coded dependency to deactivateProfile() (which is bad). You have more decoupled code in the first one, which makes it easier to change and to test.
– Aulis Ronkainen
2 days ago
3
3
Why would you do the first one? You're passing in a Setting and then ignoring its value.
– Phil N DeBlanc
2 days ago
Why would you do the first one? You're passing in a Setting and then ignoring its value.
– Phil N DeBlanc
2 days ago
42
42
I don't agree with the downvotes. While the subject matter may be considered trivial to experts, the question has merit: if dependency inversion should be used, then there should be a justification for using it.
– Flater
2 days ago
I don't agree with the downvotes. While the subject matter may be considered trivial to experts, the question has merit: if dependency inversion should be used, then there should be a justification for using it.
– Flater
2 days ago
10
10
@PhilNDeBlanc: This code is clearly oversimplified and not really indicative of real world logic. However,
deactivateProfile
suggests to me that setting the isActive
to false without caring about its previous state is the correct approach here. Calling the method inherently means that you mean to set it as inactive, not get its current (in)active status.– Flater
2 days ago
@PhilNDeBlanc: This code is clearly oversimplified and not really indicative of real world logic. However,
deactivateProfile
suggests to me that setting the isActive
to false without caring about its previous state is the correct approach here. Calling the method inherently means that you mean to set it as inactive, not get its current (in)active status.– Flater
2 days ago
2
2
Your code is not an example of dependency injection or inversion. It's an example of parameterization (which is often much better than DI).
– jpmc26
2 days ago
Your code is not an example of dependency injection or inversion. It's an example of parameterization (which is often much better than DI).
– jpmc26
2 days ago
|
show 14 more comments
9 Answers
9
active
oldest
votes
up vote
89
down vote
The advantage is that without dependency injection, your Profile class
- needs to know how to create a Settings object (violates Single Responsibility Principle)
- Always creates its Settings object the same way (creates a tight coupling between the two)
But with dependency injection
- The logic for creating Settings objects is somewhere else
- It's easy to use different kinds of Settings objects
This may seem (or even be) irrelevant in this particular case, but imagine if we're not talking about a Settings object, but a DataStore object, which might have different implementations, one that stores data in files and another that stores it in a database. And for automated tests you want a mock implementation as well. Now you really don't want the Profile class to hardcode which one it uses - and even more importantly, you really, really don't want the Profile class to know about filesystem paths, DB connections and passwords, so the creation of DataStore objects has to happen somewhere else.
20
This may seem (or even be) irrelevant in this particular case
I think it is very much relevant, in fact. How would you get the settings? A lot of systems I've seen will have a hard-coded default set of settings and a public facing configuration, so you'd need to load both and overwrite some values with the public settings. You may even need multiple sources of defaults. Perhaps you might even be getting some from disk, others from DB. So, the entire logic for even getting settings can, and often is, non-trivial - definitely not something consuming code should or would care about.
– vlaz
2 days ago
We could also mention that object initialisation for a non-trivial component, such as a web service, would make$setting = new Setting();
horrendously inefficient. Injection and object instantiation happens once.
– vikingsteve
yesterday
@vikingsteve That's not important - you'd solve that problem more easily by using e.g. a singleton. And indeed, dependency injection doesn't mean you don't create the object multiple times. It just means that your class doesn't have control over the lifetime of its dependencies - you delegate it to something in your chain of callers.
– Luaan
yesterday
6
I think using mocks for testing should have more emphasis. Chances are if you look just at the code, it's always going to be a Settings object and will never change, so passing it in seems like a wasted effort. However, the very first time you try to test a Profile object by itself without also needing a Settings object (using a mock object instead as a solution) the need is very apparent.
– JPhi1618
yesterday
1
@JPhi1618 I think the problem with emphasising "DI is for Unit Testing" is that it just leads to the question "why do I need Unit Tests". The answer may seem obvious to you, and the benefits are definitely there, but to someone just starting out, saying "you need to do this complicated-sounding thing in order to do this other complicated-sounding thing" tends to be a bit of a turn-off. So it's good to mention different advantages which might be more applicable to what they're doing right now.
– IMSoP
yesterday
|
show 4 more comments
up vote
51
down vote
Dependency Injection makes your code easier to test.
I learned this first-hand when I was tasked with fixing a hard-to-catch bug in Magento's PayPal integration.
An issue would arise when PayPal was telling Magento about a failed payment: Magento wouldn't register the failure properly.
Testing a potential fix "manually" would be very tedious: you'd need to somehow trigger a "Failed" PayPal notification. You'd have to submit an e-check, cancel it, and wait for it to error out. That means 3+ days to test a one-character code change!
Luckily, it appears that the Magento core devs who developed this function had testing in mind, and used a dependency injection pattern to make it trivial. This allows us to verify our work with a simple test case like this one:
<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
function read() {
// Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
return "HTTP/1.1 200 OKnnVERIFIED";
}
}
// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
'invoice' => '100058137', // Order ID to test against
'txn_id' => '04S87540L2309371A', // Test PayPal transaction ID
'payment_status' => 'Failed' // New payment status that Magento should ingest
);
// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());
I'm sure the DI pattern has plenty of other advantages, but increased testability is the single biggest benefit in my mind.
If you're curious about the solution to this problem, check out the GitHub repo here: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
3
Dependency injection makes code easier to test than code with hardcoded dependencies. Eliminating dependencies from business logic altogether is even better.
– Ant P
2 days ago
1
And one major way to do what @AntP suggests is via parameterization. The logic to transform results coming back from the database into the object used to fill out a page template (commonly known as a "model") shouldn't even know a fetch is happening; it just needs to get those objects as input.
– jpmc26
2 days ago
3
@jpmc26 indeed - I tend to harp on about functional core, imperative shell, which is really just a fancy name for passing data into your domain instead of injecting dependencies into it. The domain is pure, can be unit tested with no mocks and then is just wrapped in a shell that adapts things like persistence, messaging etc.
– Ant P
yesterday
I think the sole focus on testability is harmful to the adoption of DI. It makes it unappealing to people who feel like they either don't need much testing, or think they already have testing under control. I would argue it is virtually impossible to write clean, reusable code without DI. Testing is very far down on the list of benefits and it's disappointing to see this answer ranked 1st or 2nd in every question on DI's benefits.
– Carl Leth
16 hours ago
add a comment |
up vote
19
down vote
Why (what's even the issue)?
Why should I use dependency injection?
The best mnemonic I found for this is "new is glue": Every time you use new
in your code, that code is tied down to that specific implementation. If you repeatedly use new in constructors, you will create a chain of specific implementations. And because you can't "have" an instance of a class without constructing it, you can't separate that chain.
As an example, imagine you're writing a race car video game. You started with a class Game
, which creates a RaceTrack
, which creates 8 Cars
, which each create a Motor
. Now if you want a second type of Car
with a different acceleration, you will have to change every class mentioned, except maybe Game
.
Cleaner code
Is this just for cleaner architecture/code
Yes.
However, it might very well seem less clear in this situation, because it's more an example of how to do it. The actual advantage only shows when several classes are involved and is more difficult to demonstrate, but imagine you would have used DI in the previous example. The code creating all those things might look something like this:
List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
float acceleration = 0.3f;
float maxSpeed = 200.0f;
Motor motor = new Motor(acceleration, maxSpeed);
Car car = new Car(motor);
cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);
Now you don't have to make any changes in RaceTrack
, Game
, Car
, or Motor
. That means two things:
- the change is easier, because it's all in one place.
- there's no way to introduce new bugs in classes you don't change!
Performance considerations
or does this affect performance as a whole?
No. But to be completely honest with you, it might.
However, even in that case, it's such a ridiculously small amount that you don't need to care. If at some point in the future, you have to write code for a tamagotchi with the equivalent of 5Mhz CPU and 2MB RAM, then maybe you might have to care about this.
In 99.999%* of cases it will have a better performance, because you spent less time fixing bugs and more time improving your resource-heavy algorithms.
*completely made up number
Added info: "hard-coded"
Make no mistake, this is still very much "hard-coded" - the numbers are written directly in the code. Not hard-coded would mean something like storing those values in a text file - e.g. in JSON format - and then reading them from that file.
In order to do that, you have to add code for reading a file and then parsing JSON. If you consider the example again; in the non-DI version, a Car
or a Motor
now has to read a file. That doesn't sound like it makes too much sense.
In the DI version, you would add it to the code setting up the game.
2
Ad hard-coded, there is not really that much difference between code and config file. A file bundled with the application is a source even if you read dynamically. Pulling values from code to data files in json or whatever ‘config’ format helps nothing unless the values should be overridden by the user or depend on the environment.
– Jan Hudec
yesterday
2
I actually made a Tamagotchi once on an Arduino (16MHz, 2KB)
– Jungkook
yesterday
@JanHudec True. I actually had a longer explanation there, but decided to remove it to keep it shorter and focus on how it relates to DI. There's more stuff which isn't 100% correct; overall the answer is more optimized to push "the point" of DI without it getting too long. Or put differently, this is what I would have wanted to hear when I started out with DI.
– R. Schmitz
yesterday
add a comment |
up vote
7
down vote
I was always baffled by dependency injection. It seemed to only exist within Java spheres, but those spheres spoke of it with great reverence. It was one of the great Patterns, you see, which are said to bring order to chaos. But the examples were always convoluted and artificial, establishing a non-problem and then setting out to solve it by making the code more complicated.
It made more sense when a fellow Python dev imparted to me this wisdom: it's just passing arguments to functions. It's barely a pattern at all; more like a reminder that you can ask for something as an argument, even if you could have conceivably provided a reasonable value yourself.
So your question is roughly equivalent to "why should my function take arguments?" and has many of the same answers, namely: to let the caller make decisions.
This comes with a cost, of course, because now you're forcing the caller to make some decision (unless you make the argument optional), and the interface is somewhat more complex. In exchange, you gain flexibility.
So: Is there a good reason you specifically need to use this particular Setting
type/value? Is there a good reason calling code might want a different Setting
type/value? (Remember, tests are code!)
New contributor
1
Yep. IoC finally clicked to me when I realised that it's simply about "letting the caller make decisions". That IoC means the control of the component is shifted from the component's author to the component's user. And since at that point I've already had enough gripes with software that thought itself smarter than me, I was instantly sold on the DI approach.
– Joker_vD
yesterday
add a comment |
up vote
6
down vote
The example you give is not dependency injection in the classical sense. Dependency injection usually refers to passing objects in a constructor or by using "setter injection" just after the object is created, in order to set a value on a field in a newly created object.
Your example passes an object as an argument to an instance method. This instance method then modifies a field on that object. Dependency injection? No. Breaking encapsulation and data hiding? Absolutely!
Now, if the code was like this:
class Profile {
private $settings;
public function __construct(Settings $settings) {
$this->settings = $settings;
}
public function deactive() {
$this->settings->isActive = false;
}
}
Then I would say you are using dependency injection. The notable difference is a Settings
object being passed in to the constructor or a Profile
object.
This is useful if the Settings object is expensive or complex to construct, or Settings is an interface or abstract class where multiple concrete implementations exist in order to change run time behavior.
Since you are directly accessing a field on the Settings object rather than calling a method, you can't take advantage of Polymorphism, which is one of the benefits of dependency injection.
It looks like the Settings for a Profile are specific to that profile. In this case I would do one of the following:
Instantiate the Settings object inside the Profile constructor
Pass the Settings object in the constructor and copy over individual fields that apply to the Profile
Honestly, by passing the Settings object in to deactivateProfile
and then modifying an internal field of the Settings object is a code smell. The Settings object should be the only one modifying its internal fields.
5
The example you give is not dependency injection in the classical sense.
-- It doesn't matter. OO people have objects on the brain, but you're still handing a dependency to something.
– Robert Harvey
2 days ago
When you talk about “in the classical sense”, you are, as @RobertHarvey says, speaking purely in OO terms. In functional programming for example, injecting one function into another (higher order functions) is that paradigm’s classical example of dependency injection.
– David Arno
2 days ago
3
@RobertHarvey: I guess I was taking too many liberties with "dependency injection." The place people most often think of the term and use the term is in reference to fields on an object being "injected" at the time of object construction, or immediately after in the case of setter injection.
– Greg Burghardt
2 days ago
@DavidArno: Yes, you are correct. The OP seems to have an object oriented PHP code snippet in the question, so I was answering in that regard only, and not addressing functional programming --- all though with PHP you could ask the same question from the standpoint of functional programming.
– Greg Burghardt
2 days ago
add a comment |
up vote
4
down vote
I know I'm coming late to this party but I feel an important point is being missed.
Why should I do this:
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
You shouldn't. But not because Dependency Injection is a bad idea. It's because this is doing it wrong.
Lets look at this things using code. We're going to do this:
$profile = new Profile();
$profile->deactivateProfile($setting);
when we get about the same thing out of this:
$setting->isActive = false; // Deactivate profile
So of course it seems like a waste of time. It is when you do it this way. This is not the best use of Dependency Injection. It's not even the best use of a class.
Now what if instead we had this:
$profile = new Profile($setting);
$application = new Application($profile);
$application.start();
And now the application
is free to activate and deactivate the profile
without having to know anything in particular about the setting
that it's actually changing. Why is that good? In case you need to change setting. The application
is walled off from those changes so you're free to go nuts in a safe contained space without having to watch everything break as soon as you touch something.
This follows the separate construction from behavior principle. The DI pattern here is a simple one. Build everything you need at as low a level as you can, wire them together, then start all the behavior ticking with one call.
The result is you have a separate place to decide what connects to what and a different place to manage what says what to whatever.
Try that on something you have to maintain over time and see if it doesn't help.
@IMSoP Better now?
– candied_orange
yesterday
Yes, that's much more friendly :)
– IMSoP
14 hours ago
add a comment |
up vote
3
down vote
As a customer, when you hire a mechanic to do something to your car, do you expect that mechanic to build a car from scratch only to then work with it? No, you give the mechanic the car you want them to work on.
As the garage owner, when you instruct a mechanic to do something to a car, do you expect the mechanic to create his own screwdriver/wrench/car parts? No, you provide the mechanic with the parts/tools he needs to use
Why do we do this? Well, think about it. You're a garage owner who wants to hire someone to become your mechanic. You will teach them to be a mechanic (= you will write the code).
What's going to be easier:
- Teach a mechanic how to attach a spoiler to a car using a screwdriver.
- Teach a mechanic to create a car, create a spoiler, created a screwdriver
and then attach the newly created spoiler to the newly created car with the newly created screwdriver.
There are massive benefits to not having your mechanic create everything from scratch:
- Obviously, training (= development) is dramatically shortened if you just supply your mechanic with existing tools and parts.
- If the same mechanic has to perform the same job multiple times, you can make sure he reuses the screwdriver instead of always throwing the old one out and creating a new one.
- Additionally, a mechanic who has learned to create everything will need to be much more of an expert, and thus will expect a higher wage. The coding analogy here is that a class with many responsibilities is much harder to maintain than a class with a single strictly defined responsibility.
- Additionally, when new inventions hit the market and spoilers are now being made from carbon instead of plastic; you will have to retrain (= redevelop) your expert mechanic. But your "simple" mechanic won't have to be retrained as long as the spoiler can still be attached in the same way.
- Having a mechanic who doesn't rely on a car that they've built themselves means that you have a mechanic who is able to handle any car they may receive. Including cars that didn't even exist yet at the time of training the mechanic. However, your expert mechanic will not be able to build newer cars that have been created after their training.
If you hire and train the expert mechanic, you're going to end up with an employee who costs more, takes more time to perform what ought to be a simple job, and will perpetually need to be retrained whenever one of their many responsibilities need to be updated.
The development analogy is that if you use classes with hardcoded dependencies, then you're going to end up with hard to maintain classes which will need continual redevelopment/changes whenever a new version of the object (Settings
in your case) is created, and you'll have to develop internal logic for the class to have the ability to create different types of Settings
objects.
Furthermore, whoever consumes your class is now also going to have to ask the class to create the correct Settings
object, as opposed to simply being able to pass the class any Settings
object it wishes to pass. This means additional development for the consumer to figure out how to ask the class to create the right tool.
Yes, dependency inversion takes a bit more effort to write instead of hardcoding the dependency. Yes, it's annoying to have to type more.
But that is the same argument as choosing to hardcode literal values because "declaring variables takes more effort". Technically correct, but the pro's outweigh the cons by several orders of magnitude.
The benefit of dependency inversion is not experienced when you create the first version of the application. The benefit of dependency inversion is experienced when you need to change or extend that initial version. And don't trick yourself into thinking that you will get it right the first time and won't need to extend/change the code. You will have to change things.
does this affect performance as a whole?
This does not affect runtime performance of the application. But it massively impacts the development time (and therefore performance) of the developer.
2
If you removed your comment, "As a minor comment, your question focuses on dependency inversion, not dependency injection. Injection is one way to do inversion, but it's not the only way.", this would be an excellent answer. Dependency inversion can be achieved via either injection or locator/globals. The question's examples relate to injection. So the question is about dependency injection (as well as dependency inversion).
– David Arno
2 days ago
11
I think the whole car thing is a bit belaboured and confusing
– Ewan
2 days ago
3
@Flater, part of the problem is no one really seems to agree on what the difference between dependency injection, dependency inversion and inversion of control are. One thing is for certain though, a "container" most certainly isn't needed to inject a dependency into a method or constructor. Pure (or poor man's) DI specifically describes manual dependency injection. It's the only dependency injection I personally use as I dislike the "magic" associated with containers.
– David Arno
2 days ago
1
Your mechanic analogy is poor even as an analogy. If I have a mechanic trained how to fix cars, he can fix most varieties but if I come to him with a EV instead, he's likely to have no clue what to do with it. DI does not fix the polymorphic problem, all it does is help with coupling - ie how to get the car and mechanic together. A better analogy is a mechanic working for a dealer who only fixed 1 brand of car going independent and fixing other brands of car.
– gbjbaanb
yesterday
1
@gbjbaanb I think the analogy can be extended naturally to the EV example: the mechanic specifies constraints on the cars he accepts; in an OO language, that would be represented as the interface the dependency must implement. In the simple example, the constraint is "any car", but in reality it would be "any car with an internal combustion engine", or some more specific description; an EV car wouldn't meet that constraint, so wouldn't be accepted. In OO terms,NissanLeaf
would be a new class that implemented anEVCar
interface, but didn't implement theInternalCombustionCar
interface.
– IMSoP
yesterday
|
show 8 more comments
up vote
0
down vote
You should use techniques to solve the problems they're good at solving when you have those problems. Dependency inversion and injection are no different.
Dependency inversion or injection is a technique that allows your code to decide on what implementation of a method gets called at run time. This maximizes the benefits of late binding. The technique is necessary when the language does not support run time replacement of non-instance functions. For example, Java lacks a mechanism to replace calls to a static method with calls to a different implementation; contrast with Python, where all that's necessary to replace the function call is to bind the name to a different function (reassign the variable holding the function).
Why would we want to vary the implementation of the function? There's a two main reasons:
- We want to use fakes for testing purposes. This allows us to test a class that depends on a database fetch without actually connecting to the database.
- We need to support multiple implementations. For example, we might need to set up a system that supports both MySQL and PostgreSQL databases.
You may also want to take note of inversion of control containers. This is a technique that is intended to help you avoid huge, tangled construction trees that look like this pseudocode:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
It lets you register your classes and then does the construction for you:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Note that it's simplest if the classes registered can be stateless singletons.
Word of caution
Note that dependency inversion should not be your go-to answer for decoupling logic. Look for opportunities to use parameterization instead. Consider this pseudocode method for example:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
We could use dependency inversion for some parts of this method:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
But we shouldn't, at least not completely. Notice that we've created a stateful class with Querier
. It now holds a reference to some essentially global connection object. This creates problems such as difficulty in understanding the overall state of the program and how different classes coordinate with each other. Notice also that we're forced to fake out the querier or the connection if we want to test the averaging logic. Further A better approach would be to increase parameterization:
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
And the connection would be managed at some even higher level that's responsible for the operation as a whole and knows what to do with this output.
Now we can test the averaging logic completely independently of the querying, and what's more we can use it in a wider variety of situations. We might question whether we even need the MyQuerier
and Averager
objects, and maybe the answer is that we don't if we don't intend to unit test StuffDoer
, and not unit testing StuffDoer
would be perfectly reasonable since it's so tightly coupled to the database. It might make more sense to just let integration tests cover it. In that case, we might be fine making fetchAboveMin
and averageData
into static methods.
1
"Dependency injection is a technique that is intended to help you avoid huge, tangled construction trees...". Your first example after this claim is an example of pure, or poor man's, dependency injection. The second is an example of using an IoC container to "automate" the injection of those dependencies. Both are examples of dependency injection in action.
– David Arno
2 days ago
@DavidArno Yeah, you're right. I've adjusted the terminology.
– jpmc26
2 days ago
There's a third main reason to vary the implementation, or at least design the code assuming that the implementation can vary: it incentivizes developer to have loose coupling and avoid writing code that will be hard to change should a new implementation be implemented at some time in the future. And while that may not be a priority in some projects (e.g. knowing that the application will never be revisited after its initial release), it will be in others (e.g. where the company business model specifically tries to offer extended support/extension of their applications).
– Flater
17 hours ago
@Flater Dependency injection still results in strong coupling. It ties the logic to a particular interface and requires the code in question to know about whatever that interface does. Consider code that transforms results fetched from a DB. If I use DI to separate them, then the code still needs to know that a DB fetch happens and invoke it. The better solution is if the transformation code doesn't even know a fetch is happening. The only way you can do that is if the results of the fetch are passed in by a caller, rather than injecting the fetcher.
– jpmc26
7 hours ago
@jpmc26 But in your (comment) example the database didn't even need to be a dependency to begin with. Of course avoiding dependencies is better for loose coupling, but DI focuses on implementing dependencies that are needed.
– Flater
7 hours ago
|
show 2 more comments
up vote
0
down vote
Like all patterns, it is very valid to ask "why" to avoid bloated designs.
For dependency injection, this is easily seen by thinking of the two, arguably, most important facets of OOP design...
Low coupling
Coupling in computer programming:
In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.
You want to achieve low coupling. Two things being strongly coupled means that if you change the one, you very likely have to change the other. Bugs or restrictions in the one likely will induce bugs/restrictions in the other; and so on.
One class instantiating objects of the others is a very strong coupling, because the one needs to know about the existence of the other; it needs to know how to instantiate it (which arguments the constructor needs), and those arguments need to be available when calling the constructor. Also, depending on whether the language needs explicit deconstruction (C++), this will introduce further complications. If you introduce new classes (i.e., a NextSettings
or whatever), you have to go back to the original class and add more calls to their constructors.
High cohesion
Cohesion:
In computer programming, cohesion refers to the degree to which the elements inside a module belong together.
This is the other side of the coin. If you look at one unit of code (one method, one class, one package etc.), you want to have all code inside that unit to have as few responsibilities as possible.
A basic example for this would be the MVC pattern: you clearly separate the domain model from the view (GUI) and a control layer that glues those together.
This avoids code bloat where you get large chunks that do lots of different things; if you wish to change some part of it, you have to keep track of all the other features as well, trying to avoid bugs, etc.; and you quickly program yourself into a hole where it's very hard to get out.
With dependency injection, you delegate the creation or keeping track of, well, dependencies to whatever classes (or configuration files) which implement your DI. Other classes will not care much about what exactly is going on - they will be working with some generic interfaces, and have no idea what the actual implementation is, which means they are not responsible for the other stuff.
add a comment |
protected by gnat yesterday
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?
9 Answers
9
active
oldest
votes
9 Answers
9
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
89
down vote
The advantage is that without dependency injection, your Profile class
- needs to know how to create a Settings object (violates Single Responsibility Principle)
- Always creates its Settings object the same way (creates a tight coupling between the two)
But with dependency injection
- The logic for creating Settings objects is somewhere else
- It's easy to use different kinds of Settings objects
This may seem (or even be) irrelevant in this particular case, but imagine if we're not talking about a Settings object, but a DataStore object, which might have different implementations, one that stores data in files and another that stores it in a database. And for automated tests you want a mock implementation as well. Now you really don't want the Profile class to hardcode which one it uses - and even more importantly, you really, really don't want the Profile class to know about filesystem paths, DB connections and passwords, so the creation of DataStore objects has to happen somewhere else.
20
This may seem (or even be) irrelevant in this particular case
I think it is very much relevant, in fact. How would you get the settings? A lot of systems I've seen will have a hard-coded default set of settings and a public facing configuration, so you'd need to load both and overwrite some values with the public settings. You may even need multiple sources of defaults. Perhaps you might even be getting some from disk, others from DB. So, the entire logic for even getting settings can, and often is, non-trivial - definitely not something consuming code should or would care about.
– vlaz
2 days ago
We could also mention that object initialisation for a non-trivial component, such as a web service, would make$setting = new Setting();
horrendously inefficient. Injection and object instantiation happens once.
– vikingsteve
yesterday
@vikingsteve That's not important - you'd solve that problem more easily by using e.g. a singleton. And indeed, dependency injection doesn't mean you don't create the object multiple times. It just means that your class doesn't have control over the lifetime of its dependencies - you delegate it to something in your chain of callers.
– Luaan
yesterday
6
I think using mocks for testing should have more emphasis. Chances are if you look just at the code, it's always going to be a Settings object and will never change, so passing it in seems like a wasted effort. However, the very first time you try to test a Profile object by itself without also needing a Settings object (using a mock object instead as a solution) the need is very apparent.
– JPhi1618
yesterday
1
@JPhi1618 I think the problem with emphasising "DI is for Unit Testing" is that it just leads to the question "why do I need Unit Tests". The answer may seem obvious to you, and the benefits are definitely there, but to someone just starting out, saying "you need to do this complicated-sounding thing in order to do this other complicated-sounding thing" tends to be a bit of a turn-off. So it's good to mention different advantages which might be more applicable to what they're doing right now.
– IMSoP
yesterday
|
show 4 more comments
up vote
89
down vote
The advantage is that without dependency injection, your Profile class
- needs to know how to create a Settings object (violates Single Responsibility Principle)
- Always creates its Settings object the same way (creates a tight coupling between the two)
But with dependency injection
- The logic for creating Settings objects is somewhere else
- It's easy to use different kinds of Settings objects
This may seem (or even be) irrelevant in this particular case, but imagine if we're not talking about a Settings object, but a DataStore object, which might have different implementations, one that stores data in files and another that stores it in a database. And for automated tests you want a mock implementation as well. Now you really don't want the Profile class to hardcode which one it uses - and even more importantly, you really, really don't want the Profile class to know about filesystem paths, DB connections and passwords, so the creation of DataStore objects has to happen somewhere else.
20
This may seem (or even be) irrelevant in this particular case
I think it is very much relevant, in fact. How would you get the settings? A lot of systems I've seen will have a hard-coded default set of settings and a public facing configuration, so you'd need to load both and overwrite some values with the public settings. You may even need multiple sources of defaults. Perhaps you might even be getting some from disk, others from DB. So, the entire logic for even getting settings can, and often is, non-trivial - definitely not something consuming code should or would care about.
– vlaz
2 days ago
We could also mention that object initialisation for a non-trivial component, such as a web service, would make$setting = new Setting();
horrendously inefficient. Injection and object instantiation happens once.
– vikingsteve
yesterday
@vikingsteve That's not important - you'd solve that problem more easily by using e.g. a singleton. And indeed, dependency injection doesn't mean you don't create the object multiple times. It just means that your class doesn't have control over the lifetime of its dependencies - you delegate it to something in your chain of callers.
– Luaan
yesterday
6
I think using mocks for testing should have more emphasis. Chances are if you look just at the code, it's always going to be a Settings object and will never change, so passing it in seems like a wasted effort. However, the very first time you try to test a Profile object by itself without also needing a Settings object (using a mock object instead as a solution) the need is very apparent.
– JPhi1618
yesterday
1
@JPhi1618 I think the problem with emphasising "DI is for Unit Testing" is that it just leads to the question "why do I need Unit Tests". The answer may seem obvious to you, and the benefits are definitely there, but to someone just starting out, saying "you need to do this complicated-sounding thing in order to do this other complicated-sounding thing" tends to be a bit of a turn-off. So it's good to mention different advantages which might be more applicable to what they're doing right now.
– IMSoP
yesterday
|
show 4 more comments
up vote
89
down vote
up vote
89
down vote
The advantage is that without dependency injection, your Profile class
- needs to know how to create a Settings object (violates Single Responsibility Principle)
- Always creates its Settings object the same way (creates a tight coupling between the two)
But with dependency injection
- The logic for creating Settings objects is somewhere else
- It's easy to use different kinds of Settings objects
This may seem (or even be) irrelevant in this particular case, but imagine if we're not talking about a Settings object, but a DataStore object, which might have different implementations, one that stores data in files and another that stores it in a database. And for automated tests you want a mock implementation as well. Now you really don't want the Profile class to hardcode which one it uses - and even more importantly, you really, really don't want the Profile class to know about filesystem paths, DB connections and passwords, so the creation of DataStore objects has to happen somewhere else.
The advantage is that without dependency injection, your Profile class
- needs to know how to create a Settings object (violates Single Responsibility Principle)
- Always creates its Settings object the same way (creates a tight coupling between the two)
But with dependency injection
- The logic for creating Settings objects is somewhere else
- It's easy to use different kinds of Settings objects
This may seem (or even be) irrelevant in this particular case, but imagine if we're not talking about a Settings object, but a DataStore object, which might have different implementations, one that stores data in files and another that stores it in a database. And for automated tests you want a mock implementation as well. Now you really don't want the Profile class to hardcode which one it uses - and even more importantly, you really, really don't want the Profile class to know about filesystem paths, DB connections and passwords, so the creation of DataStore objects has to happen somewhere else.
answered 2 days ago
Michael Borgwardt
42.9k1097156
42.9k1097156
20
This may seem (or even be) irrelevant in this particular case
I think it is very much relevant, in fact. How would you get the settings? A lot of systems I've seen will have a hard-coded default set of settings and a public facing configuration, so you'd need to load both and overwrite some values with the public settings. You may even need multiple sources of defaults. Perhaps you might even be getting some from disk, others from DB. So, the entire logic for even getting settings can, and often is, non-trivial - definitely not something consuming code should or would care about.
– vlaz
2 days ago
We could also mention that object initialisation for a non-trivial component, such as a web service, would make$setting = new Setting();
horrendously inefficient. Injection and object instantiation happens once.
– vikingsteve
yesterday
@vikingsteve That's not important - you'd solve that problem more easily by using e.g. a singleton. And indeed, dependency injection doesn't mean you don't create the object multiple times. It just means that your class doesn't have control over the lifetime of its dependencies - you delegate it to something in your chain of callers.
– Luaan
yesterday
6
I think using mocks for testing should have more emphasis. Chances are if you look just at the code, it's always going to be a Settings object and will never change, so passing it in seems like a wasted effort. However, the very first time you try to test a Profile object by itself without also needing a Settings object (using a mock object instead as a solution) the need is very apparent.
– JPhi1618
yesterday
1
@JPhi1618 I think the problem with emphasising "DI is for Unit Testing" is that it just leads to the question "why do I need Unit Tests". The answer may seem obvious to you, and the benefits are definitely there, but to someone just starting out, saying "you need to do this complicated-sounding thing in order to do this other complicated-sounding thing" tends to be a bit of a turn-off. So it's good to mention different advantages which might be more applicable to what they're doing right now.
– IMSoP
yesterday
|
show 4 more comments
20
This may seem (or even be) irrelevant in this particular case
I think it is very much relevant, in fact. How would you get the settings? A lot of systems I've seen will have a hard-coded default set of settings and a public facing configuration, so you'd need to load both and overwrite some values with the public settings. You may even need multiple sources of defaults. Perhaps you might even be getting some from disk, others from DB. So, the entire logic for even getting settings can, and often is, non-trivial - definitely not something consuming code should or would care about.
– vlaz
2 days ago
We could also mention that object initialisation for a non-trivial component, such as a web service, would make$setting = new Setting();
horrendously inefficient. Injection and object instantiation happens once.
– vikingsteve
yesterday
@vikingsteve That's not important - you'd solve that problem more easily by using e.g. a singleton. And indeed, dependency injection doesn't mean you don't create the object multiple times. It just means that your class doesn't have control over the lifetime of its dependencies - you delegate it to something in your chain of callers.
– Luaan
yesterday
6
I think using mocks for testing should have more emphasis. Chances are if you look just at the code, it's always going to be a Settings object and will never change, so passing it in seems like a wasted effort. However, the very first time you try to test a Profile object by itself without also needing a Settings object (using a mock object instead as a solution) the need is very apparent.
– JPhi1618
yesterday
1
@JPhi1618 I think the problem with emphasising "DI is for Unit Testing" is that it just leads to the question "why do I need Unit Tests". The answer may seem obvious to you, and the benefits are definitely there, but to someone just starting out, saying "you need to do this complicated-sounding thing in order to do this other complicated-sounding thing" tends to be a bit of a turn-off. So it's good to mention different advantages which might be more applicable to what they're doing right now.
– IMSoP
yesterday
20
20
This may seem (or even be) irrelevant in this particular case
I think it is very much relevant, in fact. How would you get the settings? A lot of systems I've seen will have a hard-coded default set of settings and a public facing configuration, so you'd need to load both and overwrite some values with the public settings. You may even need multiple sources of defaults. Perhaps you might even be getting some from disk, others from DB. So, the entire logic for even getting settings can, and often is, non-trivial - definitely not something consuming code should or would care about.– vlaz
2 days ago
This may seem (or even be) irrelevant in this particular case
I think it is very much relevant, in fact. How would you get the settings? A lot of systems I've seen will have a hard-coded default set of settings and a public facing configuration, so you'd need to load both and overwrite some values with the public settings. You may even need multiple sources of defaults. Perhaps you might even be getting some from disk, others from DB. So, the entire logic for even getting settings can, and often is, non-trivial - definitely not something consuming code should or would care about.– vlaz
2 days ago
We could also mention that object initialisation for a non-trivial component, such as a web service, would make
$setting = new Setting();
horrendously inefficient. Injection and object instantiation happens once.– vikingsteve
yesterday
We could also mention that object initialisation for a non-trivial component, such as a web service, would make
$setting = new Setting();
horrendously inefficient. Injection and object instantiation happens once.– vikingsteve
yesterday
@vikingsteve That's not important - you'd solve that problem more easily by using e.g. a singleton. And indeed, dependency injection doesn't mean you don't create the object multiple times. It just means that your class doesn't have control over the lifetime of its dependencies - you delegate it to something in your chain of callers.
– Luaan
yesterday
@vikingsteve That's not important - you'd solve that problem more easily by using e.g. a singleton. And indeed, dependency injection doesn't mean you don't create the object multiple times. It just means that your class doesn't have control over the lifetime of its dependencies - you delegate it to something in your chain of callers.
– Luaan
yesterday
6
6
I think using mocks for testing should have more emphasis. Chances are if you look just at the code, it's always going to be a Settings object and will never change, so passing it in seems like a wasted effort. However, the very first time you try to test a Profile object by itself without also needing a Settings object (using a mock object instead as a solution) the need is very apparent.
– JPhi1618
yesterday
I think using mocks for testing should have more emphasis. Chances are if you look just at the code, it's always going to be a Settings object and will never change, so passing it in seems like a wasted effort. However, the very first time you try to test a Profile object by itself without also needing a Settings object (using a mock object instead as a solution) the need is very apparent.
– JPhi1618
yesterday
1
1
@JPhi1618 I think the problem with emphasising "DI is for Unit Testing" is that it just leads to the question "why do I need Unit Tests". The answer may seem obvious to you, and the benefits are definitely there, but to someone just starting out, saying "you need to do this complicated-sounding thing in order to do this other complicated-sounding thing" tends to be a bit of a turn-off. So it's good to mention different advantages which might be more applicable to what they're doing right now.
– IMSoP
yesterday
@JPhi1618 I think the problem with emphasising "DI is for Unit Testing" is that it just leads to the question "why do I need Unit Tests". The answer may seem obvious to you, and the benefits are definitely there, but to someone just starting out, saying "you need to do this complicated-sounding thing in order to do this other complicated-sounding thing" tends to be a bit of a turn-off. So it's good to mention different advantages which might be more applicable to what they're doing right now.
– IMSoP
yesterday
|
show 4 more comments
up vote
51
down vote
Dependency Injection makes your code easier to test.
I learned this first-hand when I was tasked with fixing a hard-to-catch bug in Magento's PayPal integration.
An issue would arise when PayPal was telling Magento about a failed payment: Magento wouldn't register the failure properly.
Testing a potential fix "manually" would be very tedious: you'd need to somehow trigger a "Failed" PayPal notification. You'd have to submit an e-check, cancel it, and wait for it to error out. That means 3+ days to test a one-character code change!
Luckily, it appears that the Magento core devs who developed this function had testing in mind, and used a dependency injection pattern to make it trivial. This allows us to verify our work with a simple test case like this one:
<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
function read() {
// Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
return "HTTP/1.1 200 OKnnVERIFIED";
}
}
// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
'invoice' => '100058137', // Order ID to test against
'txn_id' => '04S87540L2309371A', // Test PayPal transaction ID
'payment_status' => 'Failed' // New payment status that Magento should ingest
);
// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());
I'm sure the DI pattern has plenty of other advantages, but increased testability is the single biggest benefit in my mind.
If you're curious about the solution to this problem, check out the GitHub repo here: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
3
Dependency injection makes code easier to test than code with hardcoded dependencies. Eliminating dependencies from business logic altogether is even better.
– Ant P
2 days ago
1
And one major way to do what @AntP suggests is via parameterization. The logic to transform results coming back from the database into the object used to fill out a page template (commonly known as a "model") shouldn't even know a fetch is happening; it just needs to get those objects as input.
– jpmc26
2 days ago
3
@jpmc26 indeed - I tend to harp on about functional core, imperative shell, which is really just a fancy name for passing data into your domain instead of injecting dependencies into it. The domain is pure, can be unit tested with no mocks and then is just wrapped in a shell that adapts things like persistence, messaging etc.
– Ant P
yesterday
I think the sole focus on testability is harmful to the adoption of DI. It makes it unappealing to people who feel like they either don't need much testing, or think they already have testing under control. I would argue it is virtually impossible to write clean, reusable code without DI. Testing is very far down on the list of benefits and it's disappointing to see this answer ranked 1st or 2nd in every question on DI's benefits.
– Carl Leth
16 hours ago
add a comment |
up vote
51
down vote
Dependency Injection makes your code easier to test.
I learned this first-hand when I was tasked with fixing a hard-to-catch bug in Magento's PayPal integration.
An issue would arise when PayPal was telling Magento about a failed payment: Magento wouldn't register the failure properly.
Testing a potential fix "manually" would be very tedious: you'd need to somehow trigger a "Failed" PayPal notification. You'd have to submit an e-check, cancel it, and wait for it to error out. That means 3+ days to test a one-character code change!
Luckily, it appears that the Magento core devs who developed this function had testing in mind, and used a dependency injection pattern to make it trivial. This allows us to verify our work with a simple test case like this one:
<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
function read() {
// Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
return "HTTP/1.1 200 OKnnVERIFIED";
}
}
// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
'invoice' => '100058137', // Order ID to test against
'txn_id' => '04S87540L2309371A', // Test PayPal transaction ID
'payment_status' => 'Failed' // New payment status that Magento should ingest
);
// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());
I'm sure the DI pattern has plenty of other advantages, but increased testability is the single biggest benefit in my mind.
If you're curious about the solution to this problem, check out the GitHub repo here: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
3
Dependency injection makes code easier to test than code with hardcoded dependencies. Eliminating dependencies from business logic altogether is even better.
– Ant P
2 days ago
1
And one major way to do what @AntP suggests is via parameterization. The logic to transform results coming back from the database into the object used to fill out a page template (commonly known as a "model") shouldn't even know a fetch is happening; it just needs to get those objects as input.
– jpmc26
2 days ago
3
@jpmc26 indeed - I tend to harp on about functional core, imperative shell, which is really just a fancy name for passing data into your domain instead of injecting dependencies into it. The domain is pure, can be unit tested with no mocks and then is just wrapped in a shell that adapts things like persistence, messaging etc.
– Ant P
yesterday
I think the sole focus on testability is harmful to the adoption of DI. It makes it unappealing to people who feel like they either don't need much testing, or think they already have testing under control. I would argue it is virtually impossible to write clean, reusable code without DI. Testing is very far down on the list of benefits and it's disappointing to see this answer ranked 1st or 2nd in every question on DI's benefits.
– Carl Leth
16 hours ago
add a comment |
up vote
51
down vote
up vote
51
down vote
Dependency Injection makes your code easier to test.
I learned this first-hand when I was tasked with fixing a hard-to-catch bug in Magento's PayPal integration.
An issue would arise when PayPal was telling Magento about a failed payment: Magento wouldn't register the failure properly.
Testing a potential fix "manually" would be very tedious: you'd need to somehow trigger a "Failed" PayPal notification. You'd have to submit an e-check, cancel it, and wait for it to error out. That means 3+ days to test a one-character code change!
Luckily, it appears that the Magento core devs who developed this function had testing in mind, and used a dependency injection pattern to make it trivial. This allows us to verify our work with a simple test case like this one:
<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
function read() {
// Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
return "HTTP/1.1 200 OKnnVERIFIED";
}
}
// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
'invoice' => '100058137', // Order ID to test against
'txn_id' => '04S87540L2309371A', // Test PayPal transaction ID
'payment_status' => 'Failed' // New payment status that Magento should ingest
);
// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());
I'm sure the DI pattern has plenty of other advantages, but increased testability is the single biggest benefit in my mind.
If you're curious about the solution to this problem, check out the GitHub repo here: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
Dependency Injection makes your code easier to test.
I learned this first-hand when I was tasked with fixing a hard-to-catch bug in Magento's PayPal integration.
An issue would arise when PayPal was telling Magento about a failed payment: Magento wouldn't register the failure properly.
Testing a potential fix "manually" would be very tedious: you'd need to somehow trigger a "Failed" PayPal notification. You'd have to submit an e-check, cancel it, and wait for it to error out. That means 3+ days to test a one-character code change!
Luckily, it appears that the Magento core devs who developed this function had testing in mind, and used a dependency injection pattern to make it trivial. This allows us to verify our work with a simple test case like this one:
<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
function read() {
// Make Magento think that PayPal said "VERIFIED", no matter what they actually said...
return "HTTP/1.1 200 OKnnVERIFIED";
}
}
// Here, we trick Magento into thinking PayPal actually sent something back.
// Magento will try to verify it against PayPal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
'invoice' => '100058137', // Order ID to test against
'txn_id' => '04S87540L2309371A', // Test PayPal transaction ID
'payment_status' => 'Failed' // New payment status that Magento should ingest
);
// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to PayPal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());
I'm sure the DI pattern has plenty of other advantages, but increased testability is the single biggest benefit in my mind.
If you're curious about the solution to this problem, check out the GitHub repo here: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
answered 2 days ago
Eric Seastrand
58039
58039
3
Dependency injection makes code easier to test than code with hardcoded dependencies. Eliminating dependencies from business logic altogether is even better.
– Ant P
2 days ago
1
And one major way to do what @AntP suggests is via parameterization. The logic to transform results coming back from the database into the object used to fill out a page template (commonly known as a "model") shouldn't even know a fetch is happening; it just needs to get those objects as input.
– jpmc26
2 days ago
3
@jpmc26 indeed - I tend to harp on about functional core, imperative shell, which is really just a fancy name for passing data into your domain instead of injecting dependencies into it. The domain is pure, can be unit tested with no mocks and then is just wrapped in a shell that adapts things like persistence, messaging etc.
– Ant P
yesterday
I think the sole focus on testability is harmful to the adoption of DI. It makes it unappealing to people who feel like they either don't need much testing, or think they already have testing under control. I would argue it is virtually impossible to write clean, reusable code without DI. Testing is very far down on the list of benefits and it's disappointing to see this answer ranked 1st or 2nd in every question on DI's benefits.
– Carl Leth
16 hours ago
add a comment |
3
Dependency injection makes code easier to test than code with hardcoded dependencies. Eliminating dependencies from business logic altogether is even better.
– Ant P
2 days ago
1
And one major way to do what @AntP suggests is via parameterization. The logic to transform results coming back from the database into the object used to fill out a page template (commonly known as a "model") shouldn't even know a fetch is happening; it just needs to get those objects as input.
– jpmc26
2 days ago
3
@jpmc26 indeed - I tend to harp on about functional core, imperative shell, which is really just a fancy name for passing data into your domain instead of injecting dependencies into it. The domain is pure, can be unit tested with no mocks and then is just wrapped in a shell that adapts things like persistence, messaging etc.
– Ant P
yesterday
I think the sole focus on testability is harmful to the adoption of DI. It makes it unappealing to people who feel like they either don't need much testing, or think they already have testing under control. I would argue it is virtually impossible to write clean, reusable code without DI. Testing is very far down on the list of benefits and it's disappointing to see this answer ranked 1st or 2nd in every question on DI's benefits.
– Carl Leth
16 hours ago
3
3
Dependency injection makes code easier to test than code with hardcoded dependencies. Eliminating dependencies from business logic altogether is even better.
– Ant P
2 days ago
Dependency injection makes code easier to test than code with hardcoded dependencies. Eliminating dependencies from business logic altogether is even better.
– Ant P
2 days ago
1
1
And one major way to do what @AntP suggests is via parameterization. The logic to transform results coming back from the database into the object used to fill out a page template (commonly known as a "model") shouldn't even know a fetch is happening; it just needs to get those objects as input.
– jpmc26
2 days ago
And one major way to do what @AntP suggests is via parameterization. The logic to transform results coming back from the database into the object used to fill out a page template (commonly known as a "model") shouldn't even know a fetch is happening; it just needs to get those objects as input.
– jpmc26
2 days ago
3
3
@jpmc26 indeed - I tend to harp on about functional core, imperative shell, which is really just a fancy name for passing data into your domain instead of injecting dependencies into it. The domain is pure, can be unit tested with no mocks and then is just wrapped in a shell that adapts things like persistence, messaging etc.
– Ant P
yesterday
@jpmc26 indeed - I tend to harp on about functional core, imperative shell, which is really just a fancy name for passing data into your domain instead of injecting dependencies into it. The domain is pure, can be unit tested with no mocks and then is just wrapped in a shell that adapts things like persistence, messaging etc.
– Ant P
yesterday
I think the sole focus on testability is harmful to the adoption of DI. It makes it unappealing to people who feel like they either don't need much testing, or think they already have testing under control. I would argue it is virtually impossible to write clean, reusable code without DI. Testing is very far down on the list of benefits and it's disappointing to see this answer ranked 1st or 2nd in every question on DI's benefits.
– Carl Leth
16 hours ago
I think the sole focus on testability is harmful to the adoption of DI. It makes it unappealing to people who feel like they either don't need much testing, or think they already have testing under control. I would argue it is virtually impossible to write clean, reusable code without DI. Testing is very far down on the list of benefits and it's disappointing to see this answer ranked 1st or 2nd in every question on DI's benefits.
– Carl Leth
16 hours ago
add a comment |
up vote
19
down vote
Why (what's even the issue)?
Why should I use dependency injection?
The best mnemonic I found for this is "new is glue": Every time you use new
in your code, that code is tied down to that specific implementation. If you repeatedly use new in constructors, you will create a chain of specific implementations. And because you can't "have" an instance of a class without constructing it, you can't separate that chain.
As an example, imagine you're writing a race car video game. You started with a class Game
, which creates a RaceTrack
, which creates 8 Cars
, which each create a Motor
. Now if you want a second type of Car
with a different acceleration, you will have to change every class mentioned, except maybe Game
.
Cleaner code
Is this just for cleaner architecture/code
Yes.
However, it might very well seem less clear in this situation, because it's more an example of how to do it. The actual advantage only shows when several classes are involved and is more difficult to demonstrate, but imagine you would have used DI in the previous example. The code creating all those things might look something like this:
List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
float acceleration = 0.3f;
float maxSpeed = 200.0f;
Motor motor = new Motor(acceleration, maxSpeed);
Car car = new Car(motor);
cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);
Now you don't have to make any changes in RaceTrack
, Game
, Car
, or Motor
. That means two things:
- the change is easier, because it's all in one place.
- there's no way to introduce new bugs in classes you don't change!
Performance considerations
or does this affect performance as a whole?
No. But to be completely honest with you, it might.
However, even in that case, it's such a ridiculously small amount that you don't need to care. If at some point in the future, you have to write code for a tamagotchi with the equivalent of 5Mhz CPU and 2MB RAM, then maybe you might have to care about this.
In 99.999%* of cases it will have a better performance, because you spent less time fixing bugs and more time improving your resource-heavy algorithms.
*completely made up number
Added info: "hard-coded"
Make no mistake, this is still very much "hard-coded" - the numbers are written directly in the code. Not hard-coded would mean something like storing those values in a text file - e.g. in JSON format - and then reading them from that file.
In order to do that, you have to add code for reading a file and then parsing JSON. If you consider the example again; in the non-DI version, a Car
or a Motor
now has to read a file. That doesn't sound like it makes too much sense.
In the DI version, you would add it to the code setting up the game.
2
Ad hard-coded, there is not really that much difference between code and config file. A file bundled with the application is a source even if you read dynamically. Pulling values from code to data files in json or whatever ‘config’ format helps nothing unless the values should be overridden by the user or depend on the environment.
– Jan Hudec
yesterday
2
I actually made a Tamagotchi once on an Arduino (16MHz, 2KB)
– Jungkook
yesterday
@JanHudec True. I actually had a longer explanation there, but decided to remove it to keep it shorter and focus on how it relates to DI. There's more stuff which isn't 100% correct; overall the answer is more optimized to push "the point" of DI without it getting too long. Or put differently, this is what I would have wanted to hear when I started out with DI.
– R. Schmitz
yesterday
add a comment |
up vote
19
down vote
Why (what's even the issue)?
Why should I use dependency injection?
The best mnemonic I found for this is "new is glue": Every time you use new
in your code, that code is tied down to that specific implementation. If you repeatedly use new in constructors, you will create a chain of specific implementations. And because you can't "have" an instance of a class without constructing it, you can't separate that chain.
As an example, imagine you're writing a race car video game. You started with a class Game
, which creates a RaceTrack
, which creates 8 Cars
, which each create a Motor
. Now if you want a second type of Car
with a different acceleration, you will have to change every class mentioned, except maybe Game
.
Cleaner code
Is this just for cleaner architecture/code
Yes.
However, it might very well seem less clear in this situation, because it's more an example of how to do it. The actual advantage only shows when several classes are involved and is more difficult to demonstrate, but imagine you would have used DI in the previous example. The code creating all those things might look something like this:
List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
float acceleration = 0.3f;
float maxSpeed = 200.0f;
Motor motor = new Motor(acceleration, maxSpeed);
Car car = new Car(motor);
cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);
Now you don't have to make any changes in RaceTrack
, Game
, Car
, or Motor
. That means two things:
- the change is easier, because it's all in one place.
- there's no way to introduce new bugs in classes you don't change!
Performance considerations
or does this affect performance as a whole?
No. But to be completely honest with you, it might.
However, even in that case, it's such a ridiculously small amount that you don't need to care. If at some point in the future, you have to write code for a tamagotchi with the equivalent of 5Mhz CPU and 2MB RAM, then maybe you might have to care about this.
In 99.999%* of cases it will have a better performance, because you spent less time fixing bugs and more time improving your resource-heavy algorithms.
*completely made up number
Added info: "hard-coded"
Make no mistake, this is still very much "hard-coded" - the numbers are written directly in the code. Not hard-coded would mean something like storing those values in a text file - e.g. in JSON format - and then reading them from that file.
In order to do that, you have to add code for reading a file and then parsing JSON. If you consider the example again; in the non-DI version, a Car
or a Motor
now has to read a file. That doesn't sound like it makes too much sense.
In the DI version, you would add it to the code setting up the game.
2
Ad hard-coded, there is not really that much difference between code and config file. A file bundled with the application is a source even if you read dynamically. Pulling values from code to data files in json or whatever ‘config’ format helps nothing unless the values should be overridden by the user or depend on the environment.
– Jan Hudec
yesterday
2
I actually made a Tamagotchi once on an Arduino (16MHz, 2KB)
– Jungkook
yesterday
@JanHudec True. I actually had a longer explanation there, but decided to remove it to keep it shorter and focus on how it relates to DI. There's more stuff which isn't 100% correct; overall the answer is more optimized to push "the point" of DI without it getting too long. Or put differently, this is what I would have wanted to hear when I started out with DI.
– R. Schmitz
yesterday
add a comment |
up vote
19
down vote
up vote
19
down vote
Why (what's even the issue)?
Why should I use dependency injection?
The best mnemonic I found for this is "new is glue": Every time you use new
in your code, that code is tied down to that specific implementation. If you repeatedly use new in constructors, you will create a chain of specific implementations. And because you can't "have" an instance of a class without constructing it, you can't separate that chain.
As an example, imagine you're writing a race car video game. You started with a class Game
, which creates a RaceTrack
, which creates 8 Cars
, which each create a Motor
. Now if you want a second type of Car
with a different acceleration, you will have to change every class mentioned, except maybe Game
.
Cleaner code
Is this just for cleaner architecture/code
Yes.
However, it might very well seem less clear in this situation, because it's more an example of how to do it. The actual advantage only shows when several classes are involved and is more difficult to demonstrate, but imagine you would have used DI in the previous example. The code creating all those things might look something like this:
List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
float acceleration = 0.3f;
float maxSpeed = 200.0f;
Motor motor = new Motor(acceleration, maxSpeed);
Car car = new Car(motor);
cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);
Now you don't have to make any changes in RaceTrack
, Game
, Car
, or Motor
. That means two things:
- the change is easier, because it's all in one place.
- there's no way to introduce new bugs in classes you don't change!
Performance considerations
or does this affect performance as a whole?
No. But to be completely honest with you, it might.
However, even in that case, it's such a ridiculously small amount that you don't need to care. If at some point in the future, you have to write code for a tamagotchi with the equivalent of 5Mhz CPU and 2MB RAM, then maybe you might have to care about this.
In 99.999%* of cases it will have a better performance, because you spent less time fixing bugs and more time improving your resource-heavy algorithms.
*completely made up number
Added info: "hard-coded"
Make no mistake, this is still very much "hard-coded" - the numbers are written directly in the code. Not hard-coded would mean something like storing those values in a text file - e.g. in JSON format - and then reading them from that file.
In order to do that, you have to add code for reading a file and then parsing JSON. If you consider the example again; in the non-DI version, a Car
or a Motor
now has to read a file. That doesn't sound like it makes too much sense.
In the DI version, you would add it to the code setting up the game.
Why (what's even the issue)?
Why should I use dependency injection?
The best mnemonic I found for this is "new is glue": Every time you use new
in your code, that code is tied down to that specific implementation. If you repeatedly use new in constructors, you will create a chain of specific implementations. And because you can't "have" an instance of a class without constructing it, you can't separate that chain.
As an example, imagine you're writing a race car video game. You started with a class Game
, which creates a RaceTrack
, which creates 8 Cars
, which each create a Motor
. Now if you want a second type of Car
with a different acceleration, you will have to change every class mentioned, except maybe Game
.
Cleaner code
Is this just for cleaner architecture/code
Yes.
However, it might very well seem less clear in this situation, because it's more an example of how to do it. The actual advantage only shows when several classes are involved and is more difficult to demonstrate, but imagine you would have used DI in the previous example. The code creating all those things might look something like this:
List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
float acceleration = 0.3f;
float maxSpeed = 200.0f;
Motor motor = new Motor(acceleration, maxSpeed);
Car car = new Car(motor);
cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);
Now you don't have to make any changes in RaceTrack
, Game
, Car
, or Motor
. That means two things:
- the change is easier, because it's all in one place.
- there's no way to introduce new bugs in classes you don't change!
Performance considerations
or does this affect performance as a whole?
No. But to be completely honest with you, it might.
However, even in that case, it's such a ridiculously small amount that you don't need to care. If at some point in the future, you have to write code for a tamagotchi with the equivalent of 5Mhz CPU and 2MB RAM, then maybe you might have to care about this.
In 99.999%* of cases it will have a better performance, because you spent less time fixing bugs and more time improving your resource-heavy algorithms.
*completely made up number
Added info: "hard-coded"
Make no mistake, this is still very much "hard-coded" - the numbers are written directly in the code. Not hard-coded would mean something like storing those values in a text file - e.g. in JSON format - and then reading them from that file.
In order to do that, you have to add code for reading a file and then parsing JSON. If you consider the example again; in the non-DI version, a Car
or a Motor
now has to read a file. That doesn't sound like it makes too much sense.
In the DI version, you would add it to the code setting up the game.
edited 2 days ago
answered 2 days ago
R. Schmitz
1,012621
1,012621
2
Ad hard-coded, there is not really that much difference between code and config file. A file bundled with the application is a source even if you read dynamically. Pulling values from code to data files in json or whatever ‘config’ format helps nothing unless the values should be overridden by the user or depend on the environment.
– Jan Hudec
yesterday
2
I actually made a Tamagotchi once on an Arduino (16MHz, 2KB)
– Jungkook
yesterday
@JanHudec True. I actually had a longer explanation there, but decided to remove it to keep it shorter and focus on how it relates to DI. There's more stuff which isn't 100% correct; overall the answer is more optimized to push "the point" of DI without it getting too long. Or put differently, this is what I would have wanted to hear when I started out with DI.
– R. Schmitz
yesterday
add a comment |
2
Ad hard-coded, there is not really that much difference between code and config file. A file bundled with the application is a source even if you read dynamically. Pulling values from code to data files in json or whatever ‘config’ format helps nothing unless the values should be overridden by the user or depend on the environment.
– Jan Hudec
yesterday
2
I actually made a Tamagotchi once on an Arduino (16MHz, 2KB)
– Jungkook
yesterday
@JanHudec True. I actually had a longer explanation there, but decided to remove it to keep it shorter and focus on how it relates to DI. There's more stuff which isn't 100% correct; overall the answer is more optimized to push "the point" of DI without it getting too long. Or put differently, this is what I would have wanted to hear when I started out with DI.
– R. Schmitz
yesterday
2
2
Ad hard-coded, there is not really that much difference between code and config file. A file bundled with the application is a source even if you read dynamically. Pulling values from code to data files in json or whatever ‘config’ format helps nothing unless the values should be overridden by the user or depend on the environment.
– Jan Hudec
yesterday
Ad hard-coded, there is not really that much difference between code and config file. A file bundled with the application is a source even if you read dynamically. Pulling values from code to data files in json or whatever ‘config’ format helps nothing unless the values should be overridden by the user or depend on the environment.
– Jan Hudec
yesterday
2
2
I actually made a Tamagotchi once on an Arduino (16MHz, 2KB)
– Jungkook
yesterday
I actually made a Tamagotchi once on an Arduino (16MHz, 2KB)
– Jungkook
yesterday
@JanHudec True. I actually had a longer explanation there, but decided to remove it to keep it shorter and focus on how it relates to DI. There's more stuff which isn't 100% correct; overall the answer is more optimized to push "the point" of DI without it getting too long. Or put differently, this is what I would have wanted to hear when I started out with DI.
– R. Schmitz
yesterday
@JanHudec True. I actually had a longer explanation there, but decided to remove it to keep it shorter and focus on how it relates to DI. There's more stuff which isn't 100% correct; overall the answer is more optimized to push "the point" of DI without it getting too long. Or put differently, this is what I would have wanted to hear when I started out with DI.
– R. Schmitz
yesterday
add a comment |
up vote
7
down vote
I was always baffled by dependency injection. It seemed to only exist within Java spheres, but those spheres spoke of it with great reverence. It was one of the great Patterns, you see, which are said to bring order to chaos. But the examples were always convoluted and artificial, establishing a non-problem and then setting out to solve it by making the code more complicated.
It made more sense when a fellow Python dev imparted to me this wisdom: it's just passing arguments to functions. It's barely a pattern at all; more like a reminder that you can ask for something as an argument, even if you could have conceivably provided a reasonable value yourself.
So your question is roughly equivalent to "why should my function take arguments?" and has many of the same answers, namely: to let the caller make decisions.
This comes with a cost, of course, because now you're forcing the caller to make some decision (unless you make the argument optional), and the interface is somewhat more complex. In exchange, you gain flexibility.
So: Is there a good reason you specifically need to use this particular Setting
type/value? Is there a good reason calling code might want a different Setting
type/value? (Remember, tests are code!)
New contributor
1
Yep. IoC finally clicked to me when I realised that it's simply about "letting the caller make decisions". That IoC means the control of the component is shifted from the component's author to the component's user. And since at that point I've already had enough gripes with software that thought itself smarter than me, I was instantly sold on the DI approach.
– Joker_vD
yesterday
add a comment |
up vote
7
down vote
I was always baffled by dependency injection. It seemed to only exist within Java spheres, but those spheres spoke of it with great reverence. It was one of the great Patterns, you see, which are said to bring order to chaos. But the examples were always convoluted and artificial, establishing a non-problem and then setting out to solve it by making the code more complicated.
It made more sense when a fellow Python dev imparted to me this wisdom: it's just passing arguments to functions. It's barely a pattern at all; more like a reminder that you can ask for something as an argument, even if you could have conceivably provided a reasonable value yourself.
So your question is roughly equivalent to "why should my function take arguments?" and has many of the same answers, namely: to let the caller make decisions.
This comes with a cost, of course, because now you're forcing the caller to make some decision (unless you make the argument optional), and the interface is somewhat more complex. In exchange, you gain flexibility.
So: Is there a good reason you specifically need to use this particular Setting
type/value? Is there a good reason calling code might want a different Setting
type/value? (Remember, tests are code!)
New contributor
1
Yep. IoC finally clicked to me when I realised that it's simply about "letting the caller make decisions". That IoC means the control of the component is shifted from the component's author to the component's user. And since at that point I've already had enough gripes with software that thought itself smarter than me, I was instantly sold on the DI approach.
– Joker_vD
yesterday
add a comment |
up vote
7
down vote
up vote
7
down vote
I was always baffled by dependency injection. It seemed to only exist within Java spheres, but those spheres spoke of it with great reverence. It was one of the great Patterns, you see, which are said to bring order to chaos. But the examples were always convoluted and artificial, establishing a non-problem and then setting out to solve it by making the code more complicated.
It made more sense when a fellow Python dev imparted to me this wisdom: it's just passing arguments to functions. It's barely a pattern at all; more like a reminder that you can ask for something as an argument, even if you could have conceivably provided a reasonable value yourself.
So your question is roughly equivalent to "why should my function take arguments?" and has many of the same answers, namely: to let the caller make decisions.
This comes with a cost, of course, because now you're forcing the caller to make some decision (unless you make the argument optional), and the interface is somewhat more complex. In exchange, you gain flexibility.
So: Is there a good reason you specifically need to use this particular Setting
type/value? Is there a good reason calling code might want a different Setting
type/value? (Remember, tests are code!)
New contributor
I was always baffled by dependency injection. It seemed to only exist within Java spheres, but those spheres spoke of it with great reverence. It was one of the great Patterns, you see, which are said to bring order to chaos. But the examples were always convoluted and artificial, establishing a non-problem and then setting out to solve it by making the code more complicated.
It made more sense when a fellow Python dev imparted to me this wisdom: it's just passing arguments to functions. It's barely a pattern at all; more like a reminder that you can ask for something as an argument, even if you could have conceivably provided a reasonable value yourself.
So your question is roughly equivalent to "why should my function take arguments?" and has many of the same answers, namely: to let the caller make decisions.
This comes with a cost, of course, because now you're forcing the caller to make some decision (unless you make the argument optional), and the interface is somewhat more complex. In exchange, you gain flexibility.
So: Is there a good reason you specifically need to use this particular Setting
type/value? Is there a good reason calling code might want a different Setting
type/value? (Remember, tests are code!)
New contributor
New contributor
answered yesterday
Eevee
1797
1797
New contributor
New contributor
1
Yep. IoC finally clicked to me when I realised that it's simply about "letting the caller make decisions". That IoC means the control of the component is shifted from the component's author to the component's user. And since at that point I've already had enough gripes with software that thought itself smarter than me, I was instantly sold on the DI approach.
– Joker_vD
yesterday
add a comment |
1
Yep. IoC finally clicked to me when I realised that it's simply about "letting the caller make decisions". That IoC means the control of the component is shifted from the component's author to the component's user. And since at that point I've already had enough gripes with software that thought itself smarter than me, I was instantly sold on the DI approach.
– Joker_vD
yesterday
1
1
Yep. IoC finally clicked to me when I realised that it's simply about "letting the caller make decisions". That IoC means the control of the component is shifted from the component's author to the component's user. And since at that point I've already had enough gripes with software that thought itself smarter than me, I was instantly sold on the DI approach.
– Joker_vD
yesterday
Yep. IoC finally clicked to me when I realised that it's simply about "letting the caller make decisions". That IoC means the control of the component is shifted from the component's author to the component's user. And since at that point I've already had enough gripes with software that thought itself smarter than me, I was instantly sold on the DI approach.
– Joker_vD
yesterday
add a comment |
up vote
6
down vote
The example you give is not dependency injection in the classical sense. Dependency injection usually refers to passing objects in a constructor or by using "setter injection" just after the object is created, in order to set a value on a field in a newly created object.
Your example passes an object as an argument to an instance method. This instance method then modifies a field on that object. Dependency injection? No. Breaking encapsulation and data hiding? Absolutely!
Now, if the code was like this:
class Profile {
private $settings;
public function __construct(Settings $settings) {
$this->settings = $settings;
}
public function deactive() {
$this->settings->isActive = false;
}
}
Then I would say you are using dependency injection. The notable difference is a Settings
object being passed in to the constructor or a Profile
object.
This is useful if the Settings object is expensive or complex to construct, or Settings is an interface or abstract class where multiple concrete implementations exist in order to change run time behavior.
Since you are directly accessing a field on the Settings object rather than calling a method, you can't take advantage of Polymorphism, which is one of the benefits of dependency injection.
It looks like the Settings for a Profile are specific to that profile. In this case I would do one of the following:
Instantiate the Settings object inside the Profile constructor
Pass the Settings object in the constructor and copy over individual fields that apply to the Profile
Honestly, by passing the Settings object in to deactivateProfile
and then modifying an internal field of the Settings object is a code smell. The Settings object should be the only one modifying its internal fields.
5
The example you give is not dependency injection in the classical sense.
-- It doesn't matter. OO people have objects on the brain, but you're still handing a dependency to something.
– Robert Harvey
2 days ago
When you talk about “in the classical sense”, you are, as @RobertHarvey says, speaking purely in OO terms. In functional programming for example, injecting one function into another (higher order functions) is that paradigm’s classical example of dependency injection.
– David Arno
2 days ago
3
@RobertHarvey: I guess I was taking too many liberties with "dependency injection." The place people most often think of the term and use the term is in reference to fields on an object being "injected" at the time of object construction, or immediately after in the case of setter injection.
– Greg Burghardt
2 days ago
@DavidArno: Yes, you are correct. The OP seems to have an object oriented PHP code snippet in the question, so I was answering in that regard only, and not addressing functional programming --- all though with PHP you could ask the same question from the standpoint of functional programming.
– Greg Burghardt
2 days ago
add a comment |
up vote
6
down vote
The example you give is not dependency injection in the classical sense. Dependency injection usually refers to passing objects in a constructor or by using "setter injection" just after the object is created, in order to set a value on a field in a newly created object.
Your example passes an object as an argument to an instance method. This instance method then modifies a field on that object. Dependency injection? No. Breaking encapsulation and data hiding? Absolutely!
Now, if the code was like this:
class Profile {
private $settings;
public function __construct(Settings $settings) {
$this->settings = $settings;
}
public function deactive() {
$this->settings->isActive = false;
}
}
Then I would say you are using dependency injection. The notable difference is a Settings
object being passed in to the constructor or a Profile
object.
This is useful if the Settings object is expensive or complex to construct, or Settings is an interface or abstract class where multiple concrete implementations exist in order to change run time behavior.
Since you are directly accessing a field on the Settings object rather than calling a method, you can't take advantage of Polymorphism, which is one of the benefits of dependency injection.
It looks like the Settings for a Profile are specific to that profile. In this case I would do one of the following:
Instantiate the Settings object inside the Profile constructor
Pass the Settings object in the constructor and copy over individual fields that apply to the Profile
Honestly, by passing the Settings object in to deactivateProfile
and then modifying an internal field of the Settings object is a code smell. The Settings object should be the only one modifying its internal fields.
5
The example you give is not dependency injection in the classical sense.
-- It doesn't matter. OO people have objects on the brain, but you're still handing a dependency to something.
– Robert Harvey
2 days ago
When you talk about “in the classical sense”, you are, as @RobertHarvey says, speaking purely in OO terms. In functional programming for example, injecting one function into another (higher order functions) is that paradigm’s classical example of dependency injection.
– David Arno
2 days ago
3
@RobertHarvey: I guess I was taking too many liberties with "dependency injection." The place people most often think of the term and use the term is in reference to fields on an object being "injected" at the time of object construction, or immediately after in the case of setter injection.
– Greg Burghardt
2 days ago
@DavidArno: Yes, you are correct. The OP seems to have an object oriented PHP code snippet in the question, so I was answering in that regard only, and not addressing functional programming --- all though with PHP you could ask the same question from the standpoint of functional programming.
– Greg Burghardt
2 days ago
add a comment |
up vote
6
down vote
up vote
6
down vote
The example you give is not dependency injection in the classical sense. Dependency injection usually refers to passing objects in a constructor or by using "setter injection" just after the object is created, in order to set a value on a field in a newly created object.
Your example passes an object as an argument to an instance method. This instance method then modifies a field on that object. Dependency injection? No. Breaking encapsulation and data hiding? Absolutely!
Now, if the code was like this:
class Profile {
private $settings;
public function __construct(Settings $settings) {
$this->settings = $settings;
}
public function deactive() {
$this->settings->isActive = false;
}
}
Then I would say you are using dependency injection. The notable difference is a Settings
object being passed in to the constructor or a Profile
object.
This is useful if the Settings object is expensive or complex to construct, or Settings is an interface or abstract class where multiple concrete implementations exist in order to change run time behavior.
Since you are directly accessing a field on the Settings object rather than calling a method, you can't take advantage of Polymorphism, which is one of the benefits of dependency injection.
It looks like the Settings for a Profile are specific to that profile. In this case I would do one of the following:
Instantiate the Settings object inside the Profile constructor
Pass the Settings object in the constructor and copy over individual fields that apply to the Profile
Honestly, by passing the Settings object in to deactivateProfile
and then modifying an internal field of the Settings object is a code smell. The Settings object should be the only one modifying its internal fields.
The example you give is not dependency injection in the classical sense. Dependency injection usually refers to passing objects in a constructor or by using "setter injection" just after the object is created, in order to set a value on a field in a newly created object.
Your example passes an object as an argument to an instance method. This instance method then modifies a field on that object. Dependency injection? No. Breaking encapsulation and data hiding? Absolutely!
Now, if the code was like this:
class Profile {
private $settings;
public function __construct(Settings $settings) {
$this->settings = $settings;
}
public function deactive() {
$this->settings->isActive = false;
}
}
Then I would say you are using dependency injection. The notable difference is a Settings
object being passed in to the constructor or a Profile
object.
This is useful if the Settings object is expensive or complex to construct, or Settings is an interface or abstract class where multiple concrete implementations exist in order to change run time behavior.
Since you are directly accessing a field on the Settings object rather than calling a method, you can't take advantage of Polymorphism, which is one of the benefits of dependency injection.
It looks like the Settings for a Profile are specific to that profile. In this case I would do one of the following:
Instantiate the Settings object inside the Profile constructor
Pass the Settings object in the constructor and copy over individual fields that apply to the Profile
Honestly, by passing the Settings object in to deactivateProfile
and then modifying an internal field of the Settings object is a code smell. The Settings object should be the only one modifying its internal fields.
answered 2 days ago
Greg Burghardt
11.3k42755
11.3k42755
5
The example you give is not dependency injection in the classical sense.
-- It doesn't matter. OO people have objects on the brain, but you're still handing a dependency to something.
– Robert Harvey
2 days ago
When you talk about “in the classical sense”, you are, as @RobertHarvey says, speaking purely in OO terms. In functional programming for example, injecting one function into another (higher order functions) is that paradigm’s classical example of dependency injection.
– David Arno
2 days ago
3
@RobertHarvey: I guess I was taking too many liberties with "dependency injection." The place people most often think of the term and use the term is in reference to fields on an object being "injected" at the time of object construction, or immediately after in the case of setter injection.
– Greg Burghardt
2 days ago
@DavidArno: Yes, you are correct. The OP seems to have an object oriented PHP code snippet in the question, so I was answering in that regard only, and not addressing functional programming --- all though with PHP you could ask the same question from the standpoint of functional programming.
– Greg Burghardt
2 days ago
add a comment |
5
The example you give is not dependency injection in the classical sense.
-- It doesn't matter. OO people have objects on the brain, but you're still handing a dependency to something.
– Robert Harvey
2 days ago
When you talk about “in the classical sense”, you are, as @RobertHarvey says, speaking purely in OO terms. In functional programming for example, injecting one function into another (higher order functions) is that paradigm’s classical example of dependency injection.
– David Arno
2 days ago
3
@RobertHarvey: I guess I was taking too many liberties with "dependency injection." The place people most often think of the term and use the term is in reference to fields on an object being "injected" at the time of object construction, or immediately after in the case of setter injection.
– Greg Burghardt
2 days ago
@DavidArno: Yes, you are correct. The OP seems to have an object oriented PHP code snippet in the question, so I was answering in that regard only, and not addressing functional programming --- all though with PHP you could ask the same question from the standpoint of functional programming.
– Greg Burghardt
2 days ago
5
5
The example you give is not dependency injection in the classical sense.
-- It doesn't matter. OO people have objects on the brain, but you're still handing a dependency to something.– Robert Harvey
2 days ago
The example you give is not dependency injection in the classical sense.
-- It doesn't matter. OO people have objects on the brain, but you're still handing a dependency to something.– Robert Harvey
2 days ago
When you talk about “in the classical sense”, you are, as @RobertHarvey says, speaking purely in OO terms. In functional programming for example, injecting one function into another (higher order functions) is that paradigm’s classical example of dependency injection.
– David Arno
2 days ago
When you talk about “in the classical sense”, you are, as @RobertHarvey says, speaking purely in OO terms. In functional programming for example, injecting one function into another (higher order functions) is that paradigm’s classical example of dependency injection.
– David Arno
2 days ago
3
3
@RobertHarvey: I guess I was taking too many liberties with "dependency injection." The place people most often think of the term and use the term is in reference to fields on an object being "injected" at the time of object construction, or immediately after in the case of setter injection.
– Greg Burghardt
2 days ago
@RobertHarvey: I guess I was taking too many liberties with "dependency injection." The place people most often think of the term and use the term is in reference to fields on an object being "injected" at the time of object construction, or immediately after in the case of setter injection.
– Greg Burghardt
2 days ago
@DavidArno: Yes, you are correct. The OP seems to have an object oriented PHP code snippet in the question, so I was answering in that regard only, and not addressing functional programming --- all though with PHP you could ask the same question from the standpoint of functional programming.
– Greg Burghardt
2 days ago
@DavidArno: Yes, you are correct. The OP seems to have an object oriented PHP code snippet in the question, so I was answering in that regard only, and not addressing functional programming --- all though with PHP you could ask the same question from the standpoint of functional programming.
– Greg Burghardt
2 days ago
add a comment |
up vote
4
down vote
I know I'm coming late to this party but I feel an important point is being missed.
Why should I do this:
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
You shouldn't. But not because Dependency Injection is a bad idea. It's because this is doing it wrong.
Lets look at this things using code. We're going to do this:
$profile = new Profile();
$profile->deactivateProfile($setting);
when we get about the same thing out of this:
$setting->isActive = false; // Deactivate profile
So of course it seems like a waste of time. It is when you do it this way. This is not the best use of Dependency Injection. It's not even the best use of a class.
Now what if instead we had this:
$profile = new Profile($setting);
$application = new Application($profile);
$application.start();
And now the application
is free to activate and deactivate the profile
without having to know anything in particular about the setting
that it's actually changing. Why is that good? In case you need to change setting. The application
is walled off from those changes so you're free to go nuts in a safe contained space without having to watch everything break as soon as you touch something.
This follows the separate construction from behavior principle. The DI pattern here is a simple one. Build everything you need at as low a level as you can, wire them together, then start all the behavior ticking with one call.
The result is you have a separate place to decide what connects to what and a different place to manage what says what to whatever.
Try that on something you have to maintain over time and see if it doesn't help.
@IMSoP Better now?
– candied_orange
yesterday
Yes, that's much more friendly :)
– IMSoP
14 hours ago
add a comment |
up vote
4
down vote
I know I'm coming late to this party but I feel an important point is being missed.
Why should I do this:
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
You shouldn't. But not because Dependency Injection is a bad idea. It's because this is doing it wrong.
Lets look at this things using code. We're going to do this:
$profile = new Profile();
$profile->deactivateProfile($setting);
when we get about the same thing out of this:
$setting->isActive = false; // Deactivate profile
So of course it seems like a waste of time. It is when you do it this way. This is not the best use of Dependency Injection. It's not even the best use of a class.
Now what if instead we had this:
$profile = new Profile($setting);
$application = new Application($profile);
$application.start();
And now the application
is free to activate and deactivate the profile
without having to know anything in particular about the setting
that it's actually changing. Why is that good? In case you need to change setting. The application
is walled off from those changes so you're free to go nuts in a safe contained space without having to watch everything break as soon as you touch something.
This follows the separate construction from behavior principle. The DI pattern here is a simple one. Build everything you need at as low a level as you can, wire them together, then start all the behavior ticking with one call.
The result is you have a separate place to decide what connects to what and a different place to manage what says what to whatever.
Try that on something you have to maintain over time and see if it doesn't help.
@IMSoP Better now?
– candied_orange
yesterday
Yes, that's much more friendly :)
– IMSoP
14 hours ago
add a comment |
up vote
4
down vote
up vote
4
down vote
I know I'm coming late to this party but I feel an important point is being missed.
Why should I do this:
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
You shouldn't. But not because Dependency Injection is a bad idea. It's because this is doing it wrong.
Lets look at this things using code. We're going to do this:
$profile = new Profile();
$profile->deactivateProfile($setting);
when we get about the same thing out of this:
$setting->isActive = false; // Deactivate profile
So of course it seems like a waste of time. It is when you do it this way. This is not the best use of Dependency Injection. It's not even the best use of a class.
Now what if instead we had this:
$profile = new Profile($setting);
$application = new Application($profile);
$application.start();
And now the application
is free to activate and deactivate the profile
without having to know anything in particular about the setting
that it's actually changing. Why is that good? In case you need to change setting. The application
is walled off from those changes so you're free to go nuts in a safe contained space without having to watch everything break as soon as you touch something.
This follows the separate construction from behavior principle. The DI pattern here is a simple one. Build everything you need at as low a level as you can, wire them together, then start all the behavior ticking with one call.
The result is you have a separate place to decide what connects to what and a different place to manage what says what to whatever.
Try that on something you have to maintain over time and see if it doesn't help.
I know I'm coming late to this party but I feel an important point is being missed.
Why should I do this:
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
You shouldn't. But not because Dependency Injection is a bad idea. It's because this is doing it wrong.
Lets look at this things using code. We're going to do this:
$profile = new Profile();
$profile->deactivateProfile($setting);
when we get about the same thing out of this:
$setting->isActive = false; // Deactivate profile
So of course it seems like a waste of time. It is when you do it this way. This is not the best use of Dependency Injection. It's not even the best use of a class.
Now what if instead we had this:
$profile = new Profile($setting);
$application = new Application($profile);
$application.start();
And now the application
is free to activate and deactivate the profile
without having to know anything in particular about the setting
that it's actually changing. Why is that good? In case you need to change setting. The application
is walled off from those changes so you're free to go nuts in a safe contained space without having to watch everything break as soon as you touch something.
This follows the separate construction from behavior principle. The DI pattern here is a simple one. Build everything you need at as low a level as you can, wire them together, then start all the behavior ticking with one call.
The result is you have a separate place to decide what connects to what and a different place to manage what says what to whatever.
Try that on something you have to maintain over time and see if it doesn't help.
edited yesterday
answered 2 days ago
candied_orange
49.4k1686173
49.4k1686173
@IMSoP Better now?
– candied_orange
yesterday
Yes, that's much more friendly :)
– IMSoP
14 hours ago
add a comment |
@IMSoP Better now?
– candied_orange
yesterday
Yes, that's much more friendly :)
– IMSoP
14 hours ago
@IMSoP Better now?
– candied_orange
yesterday
@IMSoP Better now?
– candied_orange
yesterday
Yes, that's much more friendly :)
– IMSoP
14 hours ago
Yes, that's much more friendly :)
– IMSoP
14 hours ago
add a comment |
up vote
3
down vote
As a customer, when you hire a mechanic to do something to your car, do you expect that mechanic to build a car from scratch only to then work with it? No, you give the mechanic the car you want them to work on.
As the garage owner, when you instruct a mechanic to do something to a car, do you expect the mechanic to create his own screwdriver/wrench/car parts? No, you provide the mechanic with the parts/tools he needs to use
Why do we do this? Well, think about it. You're a garage owner who wants to hire someone to become your mechanic. You will teach them to be a mechanic (= you will write the code).
What's going to be easier:
- Teach a mechanic how to attach a spoiler to a car using a screwdriver.
- Teach a mechanic to create a car, create a spoiler, created a screwdriver
and then attach the newly created spoiler to the newly created car with the newly created screwdriver.
There are massive benefits to not having your mechanic create everything from scratch:
- Obviously, training (= development) is dramatically shortened if you just supply your mechanic with existing tools and parts.
- If the same mechanic has to perform the same job multiple times, you can make sure he reuses the screwdriver instead of always throwing the old one out and creating a new one.
- Additionally, a mechanic who has learned to create everything will need to be much more of an expert, and thus will expect a higher wage. The coding analogy here is that a class with many responsibilities is much harder to maintain than a class with a single strictly defined responsibility.
- Additionally, when new inventions hit the market and spoilers are now being made from carbon instead of plastic; you will have to retrain (= redevelop) your expert mechanic. But your "simple" mechanic won't have to be retrained as long as the spoiler can still be attached in the same way.
- Having a mechanic who doesn't rely on a car that they've built themselves means that you have a mechanic who is able to handle any car they may receive. Including cars that didn't even exist yet at the time of training the mechanic. However, your expert mechanic will not be able to build newer cars that have been created after their training.
If you hire and train the expert mechanic, you're going to end up with an employee who costs more, takes more time to perform what ought to be a simple job, and will perpetually need to be retrained whenever one of their many responsibilities need to be updated.
The development analogy is that if you use classes with hardcoded dependencies, then you're going to end up with hard to maintain classes which will need continual redevelopment/changes whenever a new version of the object (Settings
in your case) is created, and you'll have to develop internal logic for the class to have the ability to create different types of Settings
objects.
Furthermore, whoever consumes your class is now also going to have to ask the class to create the correct Settings
object, as opposed to simply being able to pass the class any Settings
object it wishes to pass. This means additional development for the consumer to figure out how to ask the class to create the right tool.
Yes, dependency inversion takes a bit more effort to write instead of hardcoding the dependency. Yes, it's annoying to have to type more.
But that is the same argument as choosing to hardcode literal values because "declaring variables takes more effort". Technically correct, but the pro's outweigh the cons by several orders of magnitude.
The benefit of dependency inversion is not experienced when you create the first version of the application. The benefit of dependency inversion is experienced when you need to change or extend that initial version. And don't trick yourself into thinking that you will get it right the first time and won't need to extend/change the code. You will have to change things.
does this affect performance as a whole?
This does not affect runtime performance of the application. But it massively impacts the development time (and therefore performance) of the developer.
2
If you removed your comment, "As a minor comment, your question focuses on dependency inversion, not dependency injection. Injection is one way to do inversion, but it's not the only way.", this would be an excellent answer. Dependency inversion can be achieved via either injection or locator/globals. The question's examples relate to injection. So the question is about dependency injection (as well as dependency inversion).
– David Arno
2 days ago
11
I think the whole car thing is a bit belaboured and confusing
– Ewan
2 days ago
3
@Flater, part of the problem is no one really seems to agree on what the difference between dependency injection, dependency inversion and inversion of control are. One thing is for certain though, a "container" most certainly isn't needed to inject a dependency into a method or constructor. Pure (or poor man's) DI specifically describes manual dependency injection. It's the only dependency injection I personally use as I dislike the "magic" associated with containers.
– David Arno
2 days ago
1
Your mechanic analogy is poor even as an analogy. If I have a mechanic trained how to fix cars, he can fix most varieties but if I come to him with a EV instead, he's likely to have no clue what to do with it. DI does not fix the polymorphic problem, all it does is help with coupling - ie how to get the car and mechanic together. A better analogy is a mechanic working for a dealer who only fixed 1 brand of car going independent and fixing other brands of car.
– gbjbaanb
yesterday
1
@gbjbaanb I think the analogy can be extended naturally to the EV example: the mechanic specifies constraints on the cars he accepts; in an OO language, that would be represented as the interface the dependency must implement. In the simple example, the constraint is "any car", but in reality it would be "any car with an internal combustion engine", or some more specific description; an EV car wouldn't meet that constraint, so wouldn't be accepted. In OO terms,NissanLeaf
would be a new class that implemented anEVCar
interface, but didn't implement theInternalCombustionCar
interface.
– IMSoP
yesterday
|
show 8 more comments
up vote
3
down vote
As a customer, when you hire a mechanic to do something to your car, do you expect that mechanic to build a car from scratch only to then work with it? No, you give the mechanic the car you want them to work on.
As the garage owner, when you instruct a mechanic to do something to a car, do you expect the mechanic to create his own screwdriver/wrench/car parts? No, you provide the mechanic with the parts/tools he needs to use
Why do we do this? Well, think about it. You're a garage owner who wants to hire someone to become your mechanic. You will teach them to be a mechanic (= you will write the code).
What's going to be easier:
- Teach a mechanic how to attach a spoiler to a car using a screwdriver.
- Teach a mechanic to create a car, create a spoiler, created a screwdriver
and then attach the newly created spoiler to the newly created car with the newly created screwdriver.
There are massive benefits to not having your mechanic create everything from scratch:
- Obviously, training (= development) is dramatically shortened if you just supply your mechanic with existing tools and parts.
- If the same mechanic has to perform the same job multiple times, you can make sure he reuses the screwdriver instead of always throwing the old one out and creating a new one.
- Additionally, a mechanic who has learned to create everything will need to be much more of an expert, and thus will expect a higher wage. The coding analogy here is that a class with many responsibilities is much harder to maintain than a class with a single strictly defined responsibility.
- Additionally, when new inventions hit the market and spoilers are now being made from carbon instead of plastic; you will have to retrain (= redevelop) your expert mechanic. But your "simple" mechanic won't have to be retrained as long as the spoiler can still be attached in the same way.
- Having a mechanic who doesn't rely on a car that they've built themselves means that you have a mechanic who is able to handle any car they may receive. Including cars that didn't even exist yet at the time of training the mechanic. However, your expert mechanic will not be able to build newer cars that have been created after their training.
If you hire and train the expert mechanic, you're going to end up with an employee who costs more, takes more time to perform what ought to be a simple job, and will perpetually need to be retrained whenever one of their many responsibilities need to be updated.
The development analogy is that if you use classes with hardcoded dependencies, then you're going to end up with hard to maintain classes which will need continual redevelopment/changes whenever a new version of the object (Settings
in your case) is created, and you'll have to develop internal logic for the class to have the ability to create different types of Settings
objects.
Furthermore, whoever consumes your class is now also going to have to ask the class to create the correct Settings
object, as opposed to simply being able to pass the class any Settings
object it wishes to pass. This means additional development for the consumer to figure out how to ask the class to create the right tool.
Yes, dependency inversion takes a bit more effort to write instead of hardcoding the dependency. Yes, it's annoying to have to type more.
But that is the same argument as choosing to hardcode literal values because "declaring variables takes more effort". Technically correct, but the pro's outweigh the cons by several orders of magnitude.
The benefit of dependency inversion is not experienced when you create the first version of the application. The benefit of dependency inversion is experienced when you need to change or extend that initial version. And don't trick yourself into thinking that you will get it right the first time and won't need to extend/change the code. You will have to change things.
does this affect performance as a whole?
This does not affect runtime performance of the application. But it massively impacts the development time (and therefore performance) of the developer.
2
If you removed your comment, "As a minor comment, your question focuses on dependency inversion, not dependency injection. Injection is one way to do inversion, but it's not the only way.", this would be an excellent answer. Dependency inversion can be achieved via either injection or locator/globals. The question's examples relate to injection. So the question is about dependency injection (as well as dependency inversion).
– David Arno
2 days ago
11
I think the whole car thing is a bit belaboured and confusing
– Ewan
2 days ago
3
@Flater, part of the problem is no one really seems to agree on what the difference between dependency injection, dependency inversion and inversion of control are. One thing is for certain though, a "container" most certainly isn't needed to inject a dependency into a method or constructor. Pure (or poor man's) DI specifically describes manual dependency injection. It's the only dependency injection I personally use as I dislike the "magic" associated with containers.
– David Arno
2 days ago
1
Your mechanic analogy is poor even as an analogy. If I have a mechanic trained how to fix cars, he can fix most varieties but if I come to him with a EV instead, he's likely to have no clue what to do with it. DI does not fix the polymorphic problem, all it does is help with coupling - ie how to get the car and mechanic together. A better analogy is a mechanic working for a dealer who only fixed 1 brand of car going independent and fixing other brands of car.
– gbjbaanb
yesterday
1
@gbjbaanb I think the analogy can be extended naturally to the EV example: the mechanic specifies constraints on the cars he accepts; in an OO language, that would be represented as the interface the dependency must implement. In the simple example, the constraint is "any car", but in reality it would be "any car with an internal combustion engine", or some more specific description; an EV car wouldn't meet that constraint, so wouldn't be accepted. In OO terms,NissanLeaf
would be a new class that implemented anEVCar
interface, but didn't implement theInternalCombustionCar
interface.
– IMSoP
yesterday
|
show 8 more comments
up vote
3
down vote
up vote
3
down vote
As a customer, when you hire a mechanic to do something to your car, do you expect that mechanic to build a car from scratch only to then work with it? No, you give the mechanic the car you want them to work on.
As the garage owner, when you instruct a mechanic to do something to a car, do you expect the mechanic to create his own screwdriver/wrench/car parts? No, you provide the mechanic with the parts/tools he needs to use
Why do we do this? Well, think about it. You're a garage owner who wants to hire someone to become your mechanic. You will teach them to be a mechanic (= you will write the code).
What's going to be easier:
- Teach a mechanic how to attach a spoiler to a car using a screwdriver.
- Teach a mechanic to create a car, create a spoiler, created a screwdriver
and then attach the newly created spoiler to the newly created car with the newly created screwdriver.
There are massive benefits to not having your mechanic create everything from scratch:
- Obviously, training (= development) is dramatically shortened if you just supply your mechanic with existing tools and parts.
- If the same mechanic has to perform the same job multiple times, you can make sure he reuses the screwdriver instead of always throwing the old one out and creating a new one.
- Additionally, a mechanic who has learned to create everything will need to be much more of an expert, and thus will expect a higher wage. The coding analogy here is that a class with many responsibilities is much harder to maintain than a class with a single strictly defined responsibility.
- Additionally, when new inventions hit the market and spoilers are now being made from carbon instead of plastic; you will have to retrain (= redevelop) your expert mechanic. But your "simple" mechanic won't have to be retrained as long as the spoiler can still be attached in the same way.
- Having a mechanic who doesn't rely on a car that they've built themselves means that you have a mechanic who is able to handle any car they may receive. Including cars that didn't even exist yet at the time of training the mechanic. However, your expert mechanic will not be able to build newer cars that have been created after their training.
If you hire and train the expert mechanic, you're going to end up with an employee who costs more, takes more time to perform what ought to be a simple job, and will perpetually need to be retrained whenever one of their many responsibilities need to be updated.
The development analogy is that if you use classes with hardcoded dependencies, then you're going to end up with hard to maintain classes which will need continual redevelopment/changes whenever a new version of the object (Settings
in your case) is created, and you'll have to develop internal logic for the class to have the ability to create different types of Settings
objects.
Furthermore, whoever consumes your class is now also going to have to ask the class to create the correct Settings
object, as opposed to simply being able to pass the class any Settings
object it wishes to pass. This means additional development for the consumer to figure out how to ask the class to create the right tool.
Yes, dependency inversion takes a bit more effort to write instead of hardcoding the dependency. Yes, it's annoying to have to type more.
But that is the same argument as choosing to hardcode literal values because "declaring variables takes more effort". Technically correct, but the pro's outweigh the cons by several orders of magnitude.
The benefit of dependency inversion is not experienced when you create the first version of the application. The benefit of dependency inversion is experienced when you need to change or extend that initial version. And don't trick yourself into thinking that you will get it right the first time and won't need to extend/change the code. You will have to change things.
does this affect performance as a whole?
This does not affect runtime performance of the application. But it massively impacts the development time (and therefore performance) of the developer.
As a customer, when you hire a mechanic to do something to your car, do you expect that mechanic to build a car from scratch only to then work with it? No, you give the mechanic the car you want them to work on.
As the garage owner, when you instruct a mechanic to do something to a car, do you expect the mechanic to create his own screwdriver/wrench/car parts? No, you provide the mechanic with the parts/tools he needs to use
Why do we do this? Well, think about it. You're a garage owner who wants to hire someone to become your mechanic. You will teach them to be a mechanic (= you will write the code).
What's going to be easier:
- Teach a mechanic how to attach a spoiler to a car using a screwdriver.
- Teach a mechanic to create a car, create a spoiler, created a screwdriver
and then attach the newly created spoiler to the newly created car with the newly created screwdriver.
There are massive benefits to not having your mechanic create everything from scratch:
- Obviously, training (= development) is dramatically shortened if you just supply your mechanic with existing tools and parts.
- If the same mechanic has to perform the same job multiple times, you can make sure he reuses the screwdriver instead of always throwing the old one out and creating a new one.
- Additionally, a mechanic who has learned to create everything will need to be much more of an expert, and thus will expect a higher wage. The coding analogy here is that a class with many responsibilities is much harder to maintain than a class with a single strictly defined responsibility.
- Additionally, when new inventions hit the market and spoilers are now being made from carbon instead of plastic; you will have to retrain (= redevelop) your expert mechanic. But your "simple" mechanic won't have to be retrained as long as the spoiler can still be attached in the same way.
- Having a mechanic who doesn't rely on a car that they've built themselves means that you have a mechanic who is able to handle any car they may receive. Including cars that didn't even exist yet at the time of training the mechanic. However, your expert mechanic will not be able to build newer cars that have been created after their training.
If you hire and train the expert mechanic, you're going to end up with an employee who costs more, takes more time to perform what ought to be a simple job, and will perpetually need to be retrained whenever one of their many responsibilities need to be updated.
The development analogy is that if you use classes with hardcoded dependencies, then you're going to end up with hard to maintain classes which will need continual redevelopment/changes whenever a new version of the object (Settings
in your case) is created, and you'll have to develop internal logic for the class to have the ability to create different types of Settings
objects.
Furthermore, whoever consumes your class is now also going to have to ask the class to create the correct Settings
object, as opposed to simply being able to pass the class any Settings
object it wishes to pass. This means additional development for the consumer to figure out how to ask the class to create the right tool.
Yes, dependency inversion takes a bit more effort to write instead of hardcoding the dependency. Yes, it's annoying to have to type more.
But that is the same argument as choosing to hardcode literal values because "declaring variables takes more effort". Technically correct, but the pro's outweigh the cons by several orders of magnitude.
The benefit of dependency inversion is not experienced when you create the first version of the application. The benefit of dependency inversion is experienced when you need to change or extend that initial version. And don't trick yourself into thinking that you will get it right the first time and won't need to extend/change the code. You will have to change things.
does this affect performance as a whole?
This does not affect runtime performance of the application. But it massively impacts the development time (and therefore performance) of the developer.
edited yesterday
answered 2 days ago
Flater
5,29511019
5,29511019
2
If you removed your comment, "As a minor comment, your question focuses on dependency inversion, not dependency injection. Injection is one way to do inversion, but it's not the only way.", this would be an excellent answer. Dependency inversion can be achieved via either injection or locator/globals. The question's examples relate to injection. So the question is about dependency injection (as well as dependency inversion).
– David Arno
2 days ago
11
I think the whole car thing is a bit belaboured and confusing
– Ewan
2 days ago
3
@Flater, part of the problem is no one really seems to agree on what the difference between dependency injection, dependency inversion and inversion of control are. One thing is for certain though, a "container" most certainly isn't needed to inject a dependency into a method or constructor. Pure (or poor man's) DI specifically describes manual dependency injection. It's the only dependency injection I personally use as I dislike the "magic" associated with containers.
– David Arno
2 days ago
1
Your mechanic analogy is poor even as an analogy. If I have a mechanic trained how to fix cars, he can fix most varieties but if I come to him with a EV instead, he's likely to have no clue what to do with it. DI does not fix the polymorphic problem, all it does is help with coupling - ie how to get the car and mechanic together. A better analogy is a mechanic working for a dealer who only fixed 1 brand of car going independent and fixing other brands of car.
– gbjbaanb
yesterday
1
@gbjbaanb I think the analogy can be extended naturally to the EV example: the mechanic specifies constraints on the cars he accepts; in an OO language, that would be represented as the interface the dependency must implement. In the simple example, the constraint is "any car", but in reality it would be "any car with an internal combustion engine", or some more specific description; an EV car wouldn't meet that constraint, so wouldn't be accepted. In OO terms,NissanLeaf
would be a new class that implemented anEVCar
interface, but didn't implement theInternalCombustionCar
interface.
– IMSoP
yesterday
|
show 8 more comments
2
If you removed your comment, "As a minor comment, your question focuses on dependency inversion, not dependency injection. Injection is one way to do inversion, but it's not the only way.", this would be an excellent answer. Dependency inversion can be achieved via either injection or locator/globals. The question's examples relate to injection. So the question is about dependency injection (as well as dependency inversion).
– David Arno
2 days ago
11
I think the whole car thing is a bit belaboured and confusing
– Ewan
2 days ago
3
@Flater, part of the problem is no one really seems to agree on what the difference between dependency injection, dependency inversion and inversion of control are. One thing is for certain though, a "container" most certainly isn't needed to inject a dependency into a method or constructor. Pure (or poor man's) DI specifically describes manual dependency injection. It's the only dependency injection I personally use as I dislike the "magic" associated with containers.
– David Arno
2 days ago
1
Your mechanic analogy is poor even as an analogy. If I have a mechanic trained how to fix cars, he can fix most varieties but if I come to him with a EV instead, he's likely to have no clue what to do with it. DI does not fix the polymorphic problem, all it does is help with coupling - ie how to get the car and mechanic together. A better analogy is a mechanic working for a dealer who only fixed 1 brand of car going independent and fixing other brands of car.
– gbjbaanb
yesterday
1
@gbjbaanb I think the analogy can be extended naturally to the EV example: the mechanic specifies constraints on the cars he accepts; in an OO language, that would be represented as the interface the dependency must implement. In the simple example, the constraint is "any car", but in reality it would be "any car with an internal combustion engine", or some more specific description; an EV car wouldn't meet that constraint, so wouldn't be accepted. In OO terms,NissanLeaf
would be a new class that implemented anEVCar
interface, but didn't implement theInternalCombustionCar
interface.
– IMSoP
yesterday
2
2
If you removed your comment, "As a minor comment, your question focuses on dependency inversion, not dependency injection. Injection is one way to do inversion, but it's not the only way.", this would be an excellent answer. Dependency inversion can be achieved via either injection or locator/globals. The question's examples relate to injection. So the question is about dependency injection (as well as dependency inversion).
– David Arno
2 days ago
If you removed your comment, "As a minor comment, your question focuses on dependency inversion, not dependency injection. Injection is one way to do inversion, but it's not the only way.", this would be an excellent answer. Dependency inversion can be achieved via either injection or locator/globals. The question's examples relate to injection. So the question is about dependency injection (as well as dependency inversion).
– David Arno
2 days ago
11
11
I think the whole car thing is a bit belaboured and confusing
– Ewan
2 days ago
I think the whole car thing is a bit belaboured and confusing
– Ewan
2 days ago
3
3
@Flater, part of the problem is no one really seems to agree on what the difference between dependency injection, dependency inversion and inversion of control are. One thing is for certain though, a "container" most certainly isn't needed to inject a dependency into a method or constructor. Pure (or poor man's) DI specifically describes manual dependency injection. It's the only dependency injection I personally use as I dislike the "magic" associated with containers.
– David Arno
2 days ago
@Flater, part of the problem is no one really seems to agree on what the difference between dependency injection, dependency inversion and inversion of control are. One thing is for certain though, a "container" most certainly isn't needed to inject a dependency into a method or constructor. Pure (or poor man's) DI specifically describes manual dependency injection. It's the only dependency injection I personally use as I dislike the "magic" associated with containers.
– David Arno
2 days ago
1
1
Your mechanic analogy is poor even as an analogy. If I have a mechanic trained how to fix cars, he can fix most varieties but if I come to him with a EV instead, he's likely to have no clue what to do with it. DI does not fix the polymorphic problem, all it does is help with coupling - ie how to get the car and mechanic together. A better analogy is a mechanic working for a dealer who only fixed 1 brand of car going independent and fixing other brands of car.
– gbjbaanb
yesterday
Your mechanic analogy is poor even as an analogy. If I have a mechanic trained how to fix cars, he can fix most varieties but if I come to him with a EV instead, he's likely to have no clue what to do with it. DI does not fix the polymorphic problem, all it does is help with coupling - ie how to get the car and mechanic together. A better analogy is a mechanic working for a dealer who only fixed 1 brand of car going independent and fixing other brands of car.
– gbjbaanb
yesterday
1
1
@gbjbaanb I think the analogy can be extended naturally to the EV example: the mechanic specifies constraints on the cars he accepts; in an OO language, that would be represented as the interface the dependency must implement. In the simple example, the constraint is "any car", but in reality it would be "any car with an internal combustion engine", or some more specific description; an EV car wouldn't meet that constraint, so wouldn't be accepted. In OO terms,
NissanLeaf
would be a new class that implemented an EVCar
interface, but didn't implement the InternalCombustionCar
interface.– IMSoP
yesterday
@gbjbaanb I think the analogy can be extended naturally to the EV example: the mechanic specifies constraints on the cars he accepts; in an OO language, that would be represented as the interface the dependency must implement. In the simple example, the constraint is "any car", but in reality it would be "any car with an internal combustion engine", or some more specific description; an EV car wouldn't meet that constraint, so wouldn't be accepted. In OO terms,
NissanLeaf
would be a new class that implemented an EVCar
interface, but didn't implement the InternalCombustionCar
interface.– IMSoP
yesterday
|
show 8 more comments
up vote
0
down vote
You should use techniques to solve the problems they're good at solving when you have those problems. Dependency inversion and injection are no different.
Dependency inversion or injection is a technique that allows your code to decide on what implementation of a method gets called at run time. This maximizes the benefits of late binding. The technique is necessary when the language does not support run time replacement of non-instance functions. For example, Java lacks a mechanism to replace calls to a static method with calls to a different implementation; contrast with Python, where all that's necessary to replace the function call is to bind the name to a different function (reassign the variable holding the function).
Why would we want to vary the implementation of the function? There's a two main reasons:
- We want to use fakes for testing purposes. This allows us to test a class that depends on a database fetch without actually connecting to the database.
- We need to support multiple implementations. For example, we might need to set up a system that supports both MySQL and PostgreSQL databases.
You may also want to take note of inversion of control containers. This is a technique that is intended to help you avoid huge, tangled construction trees that look like this pseudocode:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
It lets you register your classes and then does the construction for you:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Note that it's simplest if the classes registered can be stateless singletons.
Word of caution
Note that dependency inversion should not be your go-to answer for decoupling logic. Look for opportunities to use parameterization instead. Consider this pseudocode method for example:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
We could use dependency inversion for some parts of this method:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
But we shouldn't, at least not completely. Notice that we've created a stateful class with Querier
. It now holds a reference to some essentially global connection object. This creates problems such as difficulty in understanding the overall state of the program and how different classes coordinate with each other. Notice also that we're forced to fake out the querier or the connection if we want to test the averaging logic. Further A better approach would be to increase parameterization:
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
And the connection would be managed at some even higher level that's responsible for the operation as a whole and knows what to do with this output.
Now we can test the averaging logic completely independently of the querying, and what's more we can use it in a wider variety of situations. We might question whether we even need the MyQuerier
and Averager
objects, and maybe the answer is that we don't if we don't intend to unit test StuffDoer
, and not unit testing StuffDoer
would be perfectly reasonable since it's so tightly coupled to the database. It might make more sense to just let integration tests cover it. In that case, we might be fine making fetchAboveMin
and averageData
into static methods.
1
"Dependency injection is a technique that is intended to help you avoid huge, tangled construction trees...". Your first example after this claim is an example of pure, or poor man's, dependency injection. The second is an example of using an IoC container to "automate" the injection of those dependencies. Both are examples of dependency injection in action.
– David Arno
2 days ago
@DavidArno Yeah, you're right. I've adjusted the terminology.
– jpmc26
2 days ago
There's a third main reason to vary the implementation, or at least design the code assuming that the implementation can vary: it incentivizes developer to have loose coupling and avoid writing code that will be hard to change should a new implementation be implemented at some time in the future. And while that may not be a priority in some projects (e.g. knowing that the application will never be revisited after its initial release), it will be in others (e.g. where the company business model specifically tries to offer extended support/extension of their applications).
– Flater
17 hours ago
@Flater Dependency injection still results in strong coupling. It ties the logic to a particular interface and requires the code in question to know about whatever that interface does. Consider code that transforms results fetched from a DB. If I use DI to separate them, then the code still needs to know that a DB fetch happens and invoke it. The better solution is if the transformation code doesn't even know a fetch is happening. The only way you can do that is if the results of the fetch are passed in by a caller, rather than injecting the fetcher.
– jpmc26
7 hours ago
@jpmc26 But in your (comment) example the database didn't even need to be a dependency to begin with. Of course avoiding dependencies is better for loose coupling, but DI focuses on implementing dependencies that are needed.
– Flater
7 hours ago
|
show 2 more comments
up vote
0
down vote
You should use techniques to solve the problems they're good at solving when you have those problems. Dependency inversion and injection are no different.
Dependency inversion or injection is a technique that allows your code to decide on what implementation of a method gets called at run time. This maximizes the benefits of late binding. The technique is necessary when the language does not support run time replacement of non-instance functions. For example, Java lacks a mechanism to replace calls to a static method with calls to a different implementation; contrast with Python, where all that's necessary to replace the function call is to bind the name to a different function (reassign the variable holding the function).
Why would we want to vary the implementation of the function? There's a two main reasons:
- We want to use fakes for testing purposes. This allows us to test a class that depends on a database fetch without actually connecting to the database.
- We need to support multiple implementations. For example, we might need to set up a system that supports both MySQL and PostgreSQL databases.
You may also want to take note of inversion of control containers. This is a technique that is intended to help you avoid huge, tangled construction trees that look like this pseudocode:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
It lets you register your classes and then does the construction for you:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Note that it's simplest if the classes registered can be stateless singletons.
Word of caution
Note that dependency inversion should not be your go-to answer for decoupling logic. Look for opportunities to use parameterization instead. Consider this pseudocode method for example:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
We could use dependency inversion for some parts of this method:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
But we shouldn't, at least not completely. Notice that we've created a stateful class with Querier
. It now holds a reference to some essentially global connection object. This creates problems such as difficulty in understanding the overall state of the program and how different classes coordinate with each other. Notice also that we're forced to fake out the querier or the connection if we want to test the averaging logic. Further A better approach would be to increase parameterization:
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
And the connection would be managed at some even higher level that's responsible for the operation as a whole and knows what to do with this output.
Now we can test the averaging logic completely independently of the querying, and what's more we can use it in a wider variety of situations. We might question whether we even need the MyQuerier
and Averager
objects, and maybe the answer is that we don't if we don't intend to unit test StuffDoer
, and not unit testing StuffDoer
would be perfectly reasonable since it's so tightly coupled to the database. It might make more sense to just let integration tests cover it. In that case, we might be fine making fetchAboveMin
and averageData
into static methods.
1
"Dependency injection is a technique that is intended to help you avoid huge, tangled construction trees...". Your first example after this claim is an example of pure, or poor man's, dependency injection. The second is an example of using an IoC container to "automate" the injection of those dependencies. Both are examples of dependency injection in action.
– David Arno
2 days ago
@DavidArno Yeah, you're right. I've adjusted the terminology.
– jpmc26
2 days ago
There's a third main reason to vary the implementation, or at least design the code assuming that the implementation can vary: it incentivizes developer to have loose coupling and avoid writing code that will be hard to change should a new implementation be implemented at some time in the future. And while that may not be a priority in some projects (e.g. knowing that the application will never be revisited after its initial release), it will be in others (e.g. where the company business model specifically tries to offer extended support/extension of their applications).
– Flater
17 hours ago
@Flater Dependency injection still results in strong coupling. It ties the logic to a particular interface and requires the code in question to know about whatever that interface does. Consider code that transforms results fetched from a DB. If I use DI to separate them, then the code still needs to know that a DB fetch happens and invoke it. The better solution is if the transformation code doesn't even know a fetch is happening. The only way you can do that is if the results of the fetch are passed in by a caller, rather than injecting the fetcher.
– jpmc26
7 hours ago
@jpmc26 But in your (comment) example the database didn't even need to be a dependency to begin with. Of course avoiding dependencies is better for loose coupling, but DI focuses on implementing dependencies that are needed.
– Flater
7 hours ago
|
show 2 more comments
up vote
0
down vote
up vote
0
down vote
You should use techniques to solve the problems they're good at solving when you have those problems. Dependency inversion and injection are no different.
Dependency inversion or injection is a technique that allows your code to decide on what implementation of a method gets called at run time. This maximizes the benefits of late binding. The technique is necessary when the language does not support run time replacement of non-instance functions. For example, Java lacks a mechanism to replace calls to a static method with calls to a different implementation; contrast with Python, where all that's necessary to replace the function call is to bind the name to a different function (reassign the variable holding the function).
Why would we want to vary the implementation of the function? There's a two main reasons:
- We want to use fakes for testing purposes. This allows us to test a class that depends on a database fetch without actually connecting to the database.
- We need to support multiple implementations. For example, we might need to set up a system that supports both MySQL and PostgreSQL databases.
You may also want to take note of inversion of control containers. This is a technique that is intended to help you avoid huge, tangled construction trees that look like this pseudocode:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
It lets you register your classes and then does the construction for you:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Note that it's simplest if the classes registered can be stateless singletons.
Word of caution
Note that dependency inversion should not be your go-to answer for decoupling logic. Look for opportunities to use parameterization instead. Consider this pseudocode method for example:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
We could use dependency inversion for some parts of this method:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
But we shouldn't, at least not completely. Notice that we've created a stateful class with Querier
. It now holds a reference to some essentially global connection object. This creates problems such as difficulty in understanding the overall state of the program and how different classes coordinate with each other. Notice also that we're forced to fake out the querier or the connection if we want to test the averaging logic. Further A better approach would be to increase parameterization:
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
And the connection would be managed at some even higher level that's responsible for the operation as a whole and knows what to do with this output.
Now we can test the averaging logic completely independently of the querying, and what's more we can use it in a wider variety of situations. We might question whether we even need the MyQuerier
and Averager
objects, and maybe the answer is that we don't if we don't intend to unit test StuffDoer
, and not unit testing StuffDoer
would be perfectly reasonable since it's so tightly coupled to the database. It might make more sense to just let integration tests cover it. In that case, we might be fine making fetchAboveMin
and averageData
into static methods.
You should use techniques to solve the problems they're good at solving when you have those problems. Dependency inversion and injection are no different.
Dependency inversion or injection is a technique that allows your code to decide on what implementation of a method gets called at run time. This maximizes the benefits of late binding. The technique is necessary when the language does not support run time replacement of non-instance functions. For example, Java lacks a mechanism to replace calls to a static method with calls to a different implementation; contrast with Python, where all that's necessary to replace the function call is to bind the name to a different function (reassign the variable holding the function).
Why would we want to vary the implementation of the function? There's a two main reasons:
- We want to use fakes for testing purposes. This allows us to test a class that depends on a database fetch without actually connecting to the database.
- We need to support multiple implementations. For example, we might need to set up a system that supports both MySQL and PostgreSQL databases.
You may also want to take note of inversion of control containers. This is a technique that is intended to help you avoid huge, tangled construction trees that look like this pseudocode:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
It lets you register your classes and then does the construction for you:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Note that it's simplest if the classes registered can be stateless singletons.
Word of caution
Note that dependency inversion should not be your go-to answer for decoupling logic. Look for opportunities to use parameterization instead. Consider this pseudocode method for example:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
We could use dependency inversion for some parts of this method:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
But we shouldn't, at least not completely. Notice that we've created a stateful class with Querier
. It now holds a reference to some essentially global connection object. This creates problems such as difficulty in understanding the overall state of the program and how different classes coordinate with each other. Notice also that we're forced to fake out the querier or the connection if we want to test the averaging logic. Further A better approach would be to increase parameterization:
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
And the connection would be managed at some even higher level that's responsible for the operation as a whole and knows what to do with this output.
Now we can test the averaging logic completely independently of the querying, and what's more we can use it in a wider variety of situations. We might question whether we even need the MyQuerier
and Averager
objects, and maybe the answer is that we don't if we don't intend to unit test StuffDoer
, and not unit testing StuffDoer
would be perfectly reasonable since it's so tightly coupled to the database. It might make more sense to just let integration tests cover it. In that case, we might be fine making fetchAboveMin
and averageData
into static methods.
edited 2 days ago
answered 2 days ago
jpmc26
3,82111735
3,82111735
1
"Dependency injection is a technique that is intended to help you avoid huge, tangled construction trees...". Your first example after this claim is an example of pure, or poor man's, dependency injection. The second is an example of using an IoC container to "automate" the injection of those dependencies. Both are examples of dependency injection in action.
– David Arno
2 days ago
@DavidArno Yeah, you're right. I've adjusted the terminology.
– jpmc26
2 days ago
There's a third main reason to vary the implementation, or at least design the code assuming that the implementation can vary: it incentivizes developer to have loose coupling and avoid writing code that will be hard to change should a new implementation be implemented at some time in the future. And while that may not be a priority in some projects (e.g. knowing that the application will never be revisited after its initial release), it will be in others (e.g. where the company business model specifically tries to offer extended support/extension of their applications).
– Flater
17 hours ago
@Flater Dependency injection still results in strong coupling. It ties the logic to a particular interface and requires the code in question to know about whatever that interface does. Consider code that transforms results fetched from a DB. If I use DI to separate them, then the code still needs to know that a DB fetch happens and invoke it. The better solution is if the transformation code doesn't even know a fetch is happening. The only way you can do that is if the results of the fetch are passed in by a caller, rather than injecting the fetcher.
– jpmc26
7 hours ago
@jpmc26 But in your (comment) example the database didn't even need to be a dependency to begin with. Of course avoiding dependencies is better for loose coupling, but DI focuses on implementing dependencies that are needed.
– Flater
7 hours ago
|
show 2 more comments
1
"Dependency injection is a technique that is intended to help you avoid huge, tangled construction trees...". Your first example after this claim is an example of pure, or poor man's, dependency injection. The second is an example of using an IoC container to "automate" the injection of those dependencies. Both are examples of dependency injection in action.
– David Arno
2 days ago
@DavidArno Yeah, you're right. I've adjusted the terminology.
– jpmc26
2 days ago
There's a third main reason to vary the implementation, or at least design the code assuming that the implementation can vary: it incentivizes developer to have loose coupling and avoid writing code that will be hard to change should a new implementation be implemented at some time in the future. And while that may not be a priority in some projects (e.g. knowing that the application will never be revisited after its initial release), it will be in others (e.g. where the company business model specifically tries to offer extended support/extension of their applications).
– Flater
17 hours ago
@Flater Dependency injection still results in strong coupling. It ties the logic to a particular interface and requires the code in question to know about whatever that interface does. Consider code that transforms results fetched from a DB. If I use DI to separate them, then the code still needs to know that a DB fetch happens and invoke it. The better solution is if the transformation code doesn't even know a fetch is happening. The only way you can do that is if the results of the fetch are passed in by a caller, rather than injecting the fetcher.
– jpmc26
7 hours ago
@jpmc26 But in your (comment) example the database didn't even need to be a dependency to begin with. Of course avoiding dependencies is better for loose coupling, but DI focuses on implementing dependencies that are needed.
– Flater
7 hours ago
1
1
"Dependency injection is a technique that is intended to help you avoid huge, tangled construction trees...". Your first example after this claim is an example of pure, or poor man's, dependency injection. The second is an example of using an IoC container to "automate" the injection of those dependencies. Both are examples of dependency injection in action.
– David Arno
2 days ago
"Dependency injection is a technique that is intended to help you avoid huge, tangled construction trees...". Your first example after this claim is an example of pure, or poor man's, dependency injection. The second is an example of using an IoC container to "automate" the injection of those dependencies. Both are examples of dependency injection in action.
– David Arno
2 days ago
@DavidArno Yeah, you're right. I've adjusted the terminology.
– jpmc26
2 days ago
@DavidArno Yeah, you're right. I've adjusted the terminology.
– jpmc26
2 days ago
There's a third main reason to vary the implementation, or at least design the code assuming that the implementation can vary: it incentivizes developer to have loose coupling and avoid writing code that will be hard to change should a new implementation be implemented at some time in the future. And while that may not be a priority in some projects (e.g. knowing that the application will never be revisited after its initial release), it will be in others (e.g. where the company business model specifically tries to offer extended support/extension of their applications).
– Flater
17 hours ago
There's a third main reason to vary the implementation, or at least design the code assuming that the implementation can vary: it incentivizes developer to have loose coupling and avoid writing code that will be hard to change should a new implementation be implemented at some time in the future. And while that may not be a priority in some projects (e.g. knowing that the application will never be revisited after its initial release), it will be in others (e.g. where the company business model specifically tries to offer extended support/extension of their applications).
– Flater
17 hours ago
@Flater Dependency injection still results in strong coupling. It ties the logic to a particular interface and requires the code in question to know about whatever that interface does. Consider code that transforms results fetched from a DB. If I use DI to separate them, then the code still needs to know that a DB fetch happens and invoke it. The better solution is if the transformation code doesn't even know a fetch is happening. The only way you can do that is if the results of the fetch are passed in by a caller, rather than injecting the fetcher.
– jpmc26
7 hours ago
@Flater Dependency injection still results in strong coupling. It ties the logic to a particular interface and requires the code in question to know about whatever that interface does. Consider code that transforms results fetched from a DB. If I use DI to separate them, then the code still needs to know that a DB fetch happens and invoke it. The better solution is if the transformation code doesn't even know a fetch is happening. The only way you can do that is if the results of the fetch are passed in by a caller, rather than injecting the fetcher.
– jpmc26
7 hours ago
@jpmc26 But in your (comment) example the database didn't even need to be a dependency to begin with. Of course avoiding dependencies is better for loose coupling, but DI focuses on implementing dependencies that are needed.
– Flater
7 hours ago
@jpmc26 But in your (comment) example the database didn't even need to be a dependency to begin with. Of course avoiding dependencies is better for loose coupling, but DI focuses on implementing dependencies that are needed.
– Flater
7 hours ago
|
show 2 more comments
up vote
0
down vote
Like all patterns, it is very valid to ask "why" to avoid bloated designs.
For dependency injection, this is easily seen by thinking of the two, arguably, most important facets of OOP design...
Low coupling
Coupling in computer programming:
In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.
You want to achieve low coupling. Two things being strongly coupled means that if you change the one, you very likely have to change the other. Bugs or restrictions in the one likely will induce bugs/restrictions in the other; and so on.
One class instantiating objects of the others is a very strong coupling, because the one needs to know about the existence of the other; it needs to know how to instantiate it (which arguments the constructor needs), and those arguments need to be available when calling the constructor. Also, depending on whether the language needs explicit deconstruction (C++), this will introduce further complications. If you introduce new classes (i.e., a NextSettings
or whatever), you have to go back to the original class and add more calls to their constructors.
High cohesion
Cohesion:
In computer programming, cohesion refers to the degree to which the elements inside a module belong together.
This is the other side of the coin. If you look at one unit of code (one method, one class, one package etc.), you want to have all code inside that unit to have as few responsibilities as possible.
A basic example for this would be the MVC pattern: you clearly separate the domain model from the view (GUI) and a control layer that glues those together.
This avoids code bloat where you get large chunks that do lots of different things; if you wish to change some part of it, you have to keep track of all the other features as well, trying to avoid bugs, etc.; and you quickly program yourself into a hole where it's very hard to get out.
With dependency injection, you delegate the creation or keeping track of, well, dependencies to whatever classes (or configuration files) which implement your DI. Other classes will not care much about what exactly is going on - they will be working with some generic interfaces, and have no idea what the actual implementation is, which means they are not responsible for the other stuff.
add a comment |
up vote
0
down vote
Like all patterns, it is very valid to ask "why" to avoid bloated designs.
For dependency injection, this is easily seen by thinking of the two, arguably, most important facets of OOP design...
Low coupling
Coupling in computer programming:
In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.
You want to achieve low coupling. Two things being strongly coupled means that if you change the one, you very likely have to change the other. Bugs or restrictions in the one likely will induce bugs/restrictions in the other; and so on.
One class instantiating objects of the others is a very strong coupling, because the one needs to know about the existence of the other; it needs to know how to instantiate it (which arguments the constructor needs), and those arguments need to be available when calling the constructor. Also, depending on whether the language needs explicit deconstruction (C++), this will introduce further complications. If you introduce new classes (i.e., a NextSettings
or whatever), you have to go back to the original class and add more calls to their constructors.
High cohesion
Cohesion:
In computer programming, cohesion refers to the degree to which the elements inside a module belong together.
This is the other side of the coin. If you look at one unit of code (one method, one class, one package etc.), you want to have all code inside that unit to have as few responsibilities as possible.
A basic example for this would be the MVC pattern: you clearly separate the domain model from the view (GUI) and a control layer that glues those together.
This avoids code bloat where you get large chunks that do lots of different things; if you wish to change some part of it, you have to keep track of all the other features as well, trying to avoid bugs, etc.; and you quickly program yourself into a hole where it's very hard to get out.
With dependency injection, you delegate the creation or keeping track of, well, dependencies to whatever classes (or configuration files) which implement your DI. Other classes will not care much about what exactly is going on - they will be working with some generic interfaces, and have no idea what the actual implementation is, which means they are not responsible for the other stuff.
add a comment |
up vote
0
down vote
up vote
0
down vote
Like all patterns, it is very valid to ask "why" to avoid bloated designs.
For dependency injection, this is easily seen by thinking of the two, arguably, most important facets of OOP design...
Low coupling
Coupling in computer programming:
In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.
You want to achieve low coupling. Two things being strongly coupled means that if you change the one, you very likely have to change the other. Bugs or restrictions in the one likely will induce bugs/restrictions in the other; and so on.
One class instantiating objects of the others is a very strong coupling, because the one needs to know about the existence of the other; it needs to know how to instantiate it (which arguments the constructor needs), and those arguments need to be available when calling the constructor. Also, depending on whether the language needs explicit deconstruction (C++), this will introduce further complications. If you introduce new classes (i.e., a NextSettings
or whatever), you have to go back to the original class and add more calls to their constructors.
High cohesion
Cohesion:
In computer programming, cohesion refers to the degree to which the elements inside a module belong together.
This is the other side of the coin. If you look at one unit of code (one method, one class, one package etc.), you want to have all code inside that unit to have as few responsibilities as possible.
A basic example for this would be the MVC pattern: you clearly separate the domain model from the view (GUI) and a control layer that glues those together.
This avoids code bloat where you get large chunks that do lots of different things; if you wish to change some part of it, you have to keep track of all the other features as well, trying to avoid bugs, etc.; and you quickly program yourself into a hole where it's very hard to get out.
With dependency injection, you delegate the creation or keeping track of, well, dependencies to whatever classes (or configuration files) which implement your DI. Other classes will not care much about what exactly is going on - they will be working with some generic interfaces, and have no idea what the actual implementation is, which means they are not responsible for the other stuff.
Like all patterns, it is very valid to ask "why" to avoid bloated designs.
For dependency injection, this is easily seen by thinking of the two, arguably, most important facets of OOP design...
Low coupling
Coupling in computer programming:
In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are; the strength of the relationships between modules.
You want to achieve low coupling. Two things being strongly coupled means that if you change the one, you very likely have to change the other. Bugs or restrictions in the one likely will induce bugs/restrictions in the other; and so on.
One class instantiating objects of the others is a very strong coupling, because the one needs to know about the existence of the other; it needs to know how to instantiate it (which arguments the constructor needs), and those arguments need to be available when calling the constructor. Also, depending on whether the language needs explicit deconstruction (C++), this will introduce further complications. If you introduce new classes (i.e., a NextSettings
or whatever), you have to go back to the original class and add more calls to their constructors.
High cohesion
Cohesion:
In computer programming, cohesion refers to the degree to which the elements inside a module belong together.
This is the other side of the coin. If you look at one unit of code (one method, one class, one package etc.), you want to have all code inside that unit to have as few responsibilities as possible.
A basic example for this would be the MVC pattern: you clearly separate the domain model from the view (GUI) and a control layer that glues those together.
This avoids code bloat where you get large chunks that do lots of different things; if you wish to change some part of it, you have to keep track of all the other features as well, trying to avoid bugs, etc.; and you quickly program yourself into a hole where it's very hard to get out.
With dependency injection, you delegate the creation or keeping track of, well, dependencies to whatever classes (or configuration files) which implement your DI. Other classes will not care much about what exactly is going on - they will be working with some generic interfaces, and have no idea what the actual implementation is, which means they are not responsible for the other stuff.
answered 17 hours ago
AnoE
3,817716
3,817716
add a comment |
add a comment |
protected by gnat yesterday
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?
8
You are introducing a hard-coded dependency to deactivateProfile() (which is bad). You have more decoupled code in the first one, which makes it easier to change and to test.
– Aulis Ronkainen
2 days ago
3
Why would you do the first one? You're passing in a Setting and then ignoring its value.
– Phil N DeBlanc
2 days ago
42
I don't agree with the downvotes. While the subject matter may be considered trivial to experts, the question has merit: if dependency inversion should be used, then there should be a justification for using it.
– Flater
2 days ago
10
@PhilNDeBlanc: This code is clearly oversimplified and not really indicative of real world logic. However,
deactivateProfile
suggests to me that setting theisActive
to false without caring about its previous state is the correct approach here. Calling the method inherently means that you mean to set it as inactive, not get its current (in)active status.– Flater
2 days ago
2
Your code is not an example of dependency injection or inversion. It's an example of parameterization (which is often much better than DI).
– jpmc26
2 days ago