Dependency Injection: a discussion of the pros and cons


#1

Continuing the discussion from The PHP 7 Revolution: Return Types and Removed Artifacts:

I've created this new thread to focus on the question of DI and the arguments for and against its use. Tony presents some arguments against in his article (linked above) and I think it would be constructive to analyse the points he makes and see if they're valid.

@tony_marston404, @Hall_of_Famer, @swader, @TomB


Dependency Injection Breaks Encapsulation
This Week in .NET - 16 March, 2015
#2

I'll create a more in depth response later but as I mentioned in the other thread to understand the problems presented in Tony's article you first need to take a step back and ask whether those problems need to be solved or if they're a result of poor design earlier on.

Tony presents some problems and then provides some solutions. However, his initial analysis of the problem at hand is wrong which is why he is having to propose solutions to work around the problems that come from this.

Those statements imply that if you are not using dependency injection (DI) then you are an idiot. I take great exception to that arrogant, condescending attitude. Design patterns are an option, not a requirement. Some OO programmers attempt to go a step further by saying that not only must you use their favourite selection of design patterns, but you must also adhere to their particular method of implementation.

This is a strawman. The point he's responding to here is making a case for DI and pointing out why it's better than alternatives. Tony then reframes the original argument about whether A is demonstrably better than B and suggests its about people telling you what to do.

Original Author: DI is better and here's why
Tony: Stop telling me what I should be doing!

The attitude of these pattern junkies doesn't work with me. I will decide whether or not I will use a particular design pattern, and I will decide how I implement it. This usually means that I don't use design patterns at all, and those that I do use are implemented in my own unique way. Because of this I am widely regarded as a maverick, a heretic and an outcast by those who consider themselves to be "proper" OO programmers. If you think you have the stomach for it you can read some of my controversial views in the following articles:

Again, Tony's gripe here is that people are telling him why DI is better than alternatives and suggesting he should use it because it's better. Instead of providing any arguments about why DI is inferior to his preferred approach he only argues that he doesn't want to be told that DI is better. I'm already noticing a theme..

In this example the 'Person' class is the consumer and the 'Address' class is the dependent. Notice that the dependent object is created/instantiated at the last possible moment just before its service is consumed. This is known as the 'Just In Time' (JIT) method of instantiation. An alternative would be the 'Constructor' method as shown below:

I covered this in the last thread. Tony is creating a situation where the dependency may not be used then (correctly) identifying that it creates a performance issue. However, rather than trying to solve the original problem of the dependency not being used, he presents a solution to the potential performance issue using singletons (Which are demonstrably bad, again see the last thread for half a dozen or so references).

As an aside, the problem he solves with a singleton:

class Person {
    function doStuff () {
        ....
        $objAddress =& singleton::getInstance('address');
        $address = $objAddress->getPrimaryAddress($customer_id);
        ....
    } // doStuff
} // end class Person

Could also (and better be solved) using:

class Person {
    function doStuff (Address $address) {
        ....

        ....
    } // doStuff
} // end class Person

This has the advantage that the code is reusable. The $address instance does not have to come from the singleton, it can come from anywhere.

It also prevents me from adding arguments to the constructor of the dependent which are not actually known until I am about to consume its service

This, frankly, makes no sense and highlights a fundamental misunderstanding of DI. With DI you never need to know constructor arguments for dependencies because they are always passed in after they've been constructed.

I'll continue later, but there's some food for thought. I'll pick apart his references as well because they're equally misguided smile


#3

Dependency injection makes code more flexible and more decoupled.

  • More flexible because your class doesn't need to know about the concrete implementation. Real life example: I was writing a bunch of unit tests, and those unit test classes needed access to a logger, so I passed a logger instance into the constructor (that is, I injected the dependency). Doing it this way paid off later. We decided it would be nice if each log message would say which test case it came from. But there were hundreds of test cases, and editing them manually would have been time consuming and tedious, which is bad all by itself, but also creates a lot of opportunity for human error. Instead, I created a TestCaseLoggerDecorator class that re-implements the logger's interface and prepends the test case name to the log message. Then, instead of injecting the logger, I injected the decorator, and voila. Without any modification to any of the test cases, the log messages now included the test case name, because now the test cases weren't actually invoking the logger, they were invoking the decorator.

"What is good software architecture? For me, good design means that when I make a change, it’s as if the entire program was crafted in anticipation of it. I can solve a task with just a few choice function calls that slot in perfectly, leaving not the slightest ripple on the placid surface of the code."

  • And more decoupled because your class doesn't need to know anything of the outside world. This goes back to the original terminology, "inversion of control." If your class were to fetch its own dependencies, then it would need to know where to find them -- whether it be a container or a singleton or a global variable or whatever -- which means it would need to know about the larger application. That's bad. If details of the larger application are baked into every class, then it's harder to change the larger application, and almost impossible to reuse the class in different applications. But if instead dependencies are passed through the constructor, then your class doesn't need to know or care who created it or where it came from.

#4

Why are we even dignifying Tony's BS with its own thread here?

How in the world did he get...

Those statements imply that if you are not using dependency injection (DI) then you are an idiot.

...out of Ralph's very smartly written article?

This is also coming from a guy, who goes on to say that people who use DI or the DI pattern are lacking brain power and are blind boss following trained monkeys? Holy crap!.....that just shows us Tony's true level of argumentative prowess and his level of intelligence.

Tony is an archaic relic of old procedural times, a programming dinosaur, who thinks he must prove to the world that only he is the smartest and the greatest programmer out there. I think he should finally go into retirement and leave the world of PHP alone. A world, I say, is full of very smart and talented people, who very much understand when, where, why and how to properly use DI.

Scott


#5

Hi Scott,

I agree with you that the tone of the original article was unnecessary, and rather than respond similarly I was hoping we could use this thread to examine the reasons that Tony does give and see whether they hold up.

While that's true, I also think there are many more newer programmers out there who may not fully understand DI and might be mislead or influenced by articles such as Tony's, and I think they could benefit from a deeper discussion of the topic. Just telling them 'Tony is wrong, DI is good' plays into his hands, as we come off as just trying to argue from authority.


#6

I understand. But, I am not arguing from authority. I am arguing from the position of, "why listen to a guy who calls people basically ninnies, when they don't follow his own perceptions of proper programming?" At best all he deserves is a retort. Does he really think he can persuade people to believe his perspective is the better one, by telling them they are fools for not doing so? Those who would believe him deserve their own fate then too.

I'd be all for a proper argument/ discussion, if Tony would even closely come to the realization that the DI pattern used in modern programming makes perfect sense and is not just rarely useful, but rather often used and those who use the pattern are using it (for the most part) for a reason, like for encapsulation, decoupling of code, following the single responsibility principle for easier testing, lowering side-effects, when changing or exchanging code, etc. You know, some of the things described in SOLID, which is the foundation of any good OO application. Is DI always necessary? Absolutely not. Are we arguing that point even?

I think Tony argues too much on the basis of things not solving the customer's problems in the code, which shows his misconception. To paraphrase his general ideology, "If it doesn't help the customer, it isn't worth using". What he is missing is that DI purely solves programmer's problems. It has, in the first instance, nothing to do with the overall result from the end-user's perspective. DI is mainly used to make the programmer's life much easier, when programming a complicated application. The factors I mentioned above, because they help with better coding, ultimately DO help the customers/ end-user in faster, less costly releases. If it weren't true, DI wouldn't be found in almost any framework in any language out there, as is the case. Tony even admits he sort of uses DI in his own framework. Go figurue?

Is that arguing from authority? Nope. It is arguing from common sense, which Tony seems to sometimes lack. Ok. there. That was my second retort! LOL!

Scott


#7

It's tricky because the article rambles and rants, but below are a few of the criticisms I could distill. I'll start with what seemed like the better criticism, then it's downhill from there.

  • "Here you see that the dependent object is created within the constructor of its consumer, although the dependent is not actually accessed until later (if at all). I personally do not like this method as it is possible to create an object which is not actually used, which could lead to performance issues."

The issue is what happens when we're dealing with an expensive object. For example, it's common in web development to do something like this:

if ($form->isValid()) {
    // save to db
} else {
    // render form with errors
}

Connecting to a database can be expensive. Ideally, we want to do it only when the condition evaluates to true. If we're using a service locator, then it's admittedly straightforward:

if ($form->isValid()) {
    $db = $container->get('database');
    // ...
} else {
    // render form with errors
}

If instead we were using DI, the way we would solve this is with the proxy pattern. That is, an object that serves as a placeholder for the real object to defer its creation until we actually need it (aka lazy load).

So which is best? It can vary from one scenario to another. A (web) MVC controller is certainly a good candidate for SL rather than DI, which is probably why most (all?) frameworks do it that way. But in other scenarios, I'd wager that DI would be the better option most of the time.

It's a good topic for a nuanced article, not an "is evil" rant. wink

  • "What happens if the constructor of the dependent requires arguments which are not available until the consumer is just about to communicate with that dependent?"

In this case, the consumer isn't a consumer at all. It's a factory. That is, the consumer doesn't need an instance; it creates an instance. If, after it's been created, we proceed to do other work that uses that new instance, then chances are good those two tasks should be separated, since they have distinct responsibilities.

  • "If you build in, at great expense, the ability to make certain changes in your application architecture, but those changes are rarely made, then surely you are violating the YAGNI principle?"

The key factor, of course, is whether changes are rarely made. Once upon a time, we tried to do all the analysis and design up front. Think waterfall. But as it turns out, requirements change all the time, and therefore software changes all the time. Today, an important factor that makes an architecture good or bad is how adaptable it is to change.

  • "This "solution" is supposed to address the problem of dependencies, but what exactly is the problem? You cannot eliminate dependencies altogether as your software just would not work."

This criticism misunderstands DI altogether. DI doesn't try to eliminate dependencies. It changes (inverts) how you get them and who creates them.


#8

Really well put. As an addition, sometimes requirements for the project don't change themselves but you get another project with very similar requirements. Using DI this allows you to use the same classes externally configured in a different way. If the code is configured internally (by dictating its own dependencies) and those are different between the projects (eg. $container->get'(A') in project1 and $container->get('SpecialisedA') in project2 then you cant add a feature or fix a bug in project 1 and just replace the class in project 2 with the updated one (this applies to singletons, service locators and annotation based config).


#9

A little off-topic, but I think the argument and conclusion about Tony was made a long time ago? This was a thread back in 2010, and it seems that Tony and TomB were 'very good friends' at that point already. If you click the thread below, you will be astonished at how much detail they got into. Wow, its actually very good read. Note, back in 2010 Tony was on a different account, but it clearly was him.


#10

The alternative to using DI is NOT using DI. I choose not to use DI because I do not have the problem for which DI is the solution. DI is only useful when you want to regularly switch all the dependencies in your applications, such as from real objects to mock objects. I do not use mock objects, and I do not have to switch any dependencies anywhere in my application. So why should I pollute my application with additional code whose sole purpose is to make it easier to do something that I'm never going to do? This violates the YAGNI principle.

You are misrepresenting what I said. Even though I keep saying that I have no use for DI I am constantly being told that I should be using it anyway.

I did not say that DI is inferior, just unnecessary in the applications which I write. I do not need to switch dependencies, therefore I do not need to employ a mechanism for switching dependencies.

There may be a circumstance where a dependent object is created but never used, but I did not cover that in my coder sample. The most common example I have seen in other people's coder is where the Controller creates a database object, injects it into the Model, then activates a method on the Model to insert or update some data. But what if the data validation fails? In this scenario the Model returns control without using the database object, so all that time and effort in creating the database object was wasted. In my own application the database object is not created by the Controller and injected into the Model, it is created in the Model only after the data validation has succeeded and when database access becomes necessary.

I keep hearing that singletons are bad, but I have yet to see any proof that they are. I have been using them for over 10 years without any issues, so as far as I am concerned there are no issues. The argument that "singletons are bad" has about as much sense as the argument "inheritance breaks encapsulation". When I say "as much sense" I actually mean "no sense at all".

That is a different way, but I would not say that it is "better". You have not identified where the $address variable comes from.

In what way is that code "reusable"? Code inside a method is only reusable when that method is called from more than one place. How is the original code I had inside that method less reusable?

"From anywhere" is not good enough for me. I need to know exactly where and how the object was created and with what options, which is why I leave an object's creation to the last possible moment

You are misreading what I said. In all the examples of DI that I have seen the dependent objects are instantiated BEFORE the object which consumes them so that they can be injected into the consumer as arguments on the consumer's constructor. In other examples the dependencies are injected immediately after the consumer has been constructed. What this means is that when you get to the code inside the consumer object which uses the dependent object, it has already been created, so it is too late to have any influence over the dependent object's constructor.


#11

Can you give a specific example of why you would want the consuming object to have a say in the construction of a dependent object?


#12

Dependency injection is only useful when you regularly want to switch all the dependent objects in your application, such as switching from real objects to mock objects. I never use mock objects, I never have to switch any dependencies, so adding code to my application to make it easier to do something that I am never going to do is a violation of the YAGNI principle.

"more decoupled" is a vague and misleading term. When you talk about coupling and cohesion the terms used are "high" and "low" not "present" or "absent". Low coupling often equates to high cohesion and vice versa. Low coupling and high cohesion are supposed to be good while de-coupling is nonsense as an application which contains completely decoupled methods will not work.

I disagree. In my application if I want to create a dependent object I use my singleton::getInstance() method with the class name and the getInstance() method locates the class file for me. I certainly don't need to know anything about the rest of the application, only those parts which on which my current object depends.

I disagree. I have been using hardcoded dependencies for over 10 years without any such issues. My main ERP application has over 10 separate modules, and I often have a dependency in one object which belongs in a different module.

Why is this supposed to be an issue? An object needs to access a dependent object, so what's wrong with creating that object just before I want to use it? This takes less code to write, less code to read, and is therefore easier to maintain.


#13

I also would like to see/hear an example where the consuming object needs to have a say in the construction of the dependent object.

By chance do you support multiple databases? If so, how are you handling the differences in the calls to each database? What pattern are you using (if any)?

Do you happen to write Unit Tests for your code? Just curious how you handle dependencies when testing, as that is one aspect where I find utilizing DI to be very useful.


#14

I formed my opinion after reading the following statements from Ralph's article:

In any circle of developers that are of the object-oriented persuasion, you'll never hear an argument that dependency injection itself, is bad. In these circles, it is generally accepted that injecting dependencies is the best way to go.

followed by this

The heart of the argument is not should we be doing it (Dependency Injection), but how do we go about doing it.

His article does not explain that DI is a particular solution to a particular problem, and that you should only employ this solution if you have this particular problem, but that you should be using DI whether you have the problem or not. He is saying that you should employ DI without thinking as all the necessary thinking has already been done by your elders and betters. They have decided that that injecting dependencies is the best way to go, and the argument is not should we be doing it but how do we go about doing it.

I prefer to think for myself, thank you very much, and as I do not have the problem for which DI is the solution I choose not to implement that solution. This is in keeping with the YAGNI principle so how can it be wrong?


#15

You are all missing a fundamental point. It is simply not good enough to say "you should be using DI" without explaining when it is a good idea as it provides benefits and when it is not such a good idea. It is the blanket statement "you should be using DI whether you need it or not" to which I object. I do not have the problem for which DI is the solution, therefore I see absolutely no need to implement a solution that will never be used. This is the YAGNI principle in action.


#16

Typical. You can't attack the message so you attack the messenger.


#17

I disagree with your description of DI. Its sole purpose is to make it easier to switch dependencies in your application, such as replacing real objects with mock objects. It has nothing to do with encapsulation, coupling, or the Single Responsibility Principle. I never need to switch any dependencies, therefore I have no need for DI.

I appear to be the only one who is arguing that DI is not necessary, and I am constantly being attacked for holding such a heretical view.

DI is a particular solution to a particular problem, not a general "silver bullet" for a whole host of problems.

My argument is that if you don't have the problem for which DI is the solution then implementing DI is a waste of time, and because it pollutes your application with unnecessary code it effectively makes your application harder to read and harder to maintain.

Just because lots of frameworks use DI does not mean that all frameworks should use it. Some developers use it not because they want to but because they are forced to. They are told by their elders and betters that they should use it, so they obey without question. I am not such a weak minded person. When someone says "do it this way" I will always ask "Why should I?" If the arguments fail to impress me then it is my God-given right to say "NO!"

When I say "sort of" I mean that before I call a page controller I inject the name of the view and the model via global variables as in the following:

<?php
$table_id = "person";                      // identify the Model
$screen   = 'person.detail.screen.inc';    // identify the View
require 'std.enquire1.inc';                // activate the Controller
?>

This means that I can reuse the ENQUIRE1 controller as many times as I like with different models and views, and without having to make any changes to the controller.


#18

Can you give me an argument as to why it shouldn't?


#19

Why defensive? He simply was looking for an example of something you felt was an important reason to not implement DI.

I can think of many reasons why the consuming class wouldn't need to influence how the dependency was created. As for one, it blurs the lines on which object is dependent on the other, it all reality, it reverses the roles, the consuming class is now a dependency for the dependent object (confused? I am).

Just trying to get an idea of the technique you used or needed to use for this task (not trying to determine right or wrong here). Just want to understand that perspective better and an example would go a long way for that.


#20

You stated it as a reason for needing to instantiate the dependent object within the consumer, and I don't see why you would need to do that, so I'm asking if you would provide an example. Are you not interested in having a civilized and constructive exchange of viewpoints?