What is dependency injection?
For quite some time, I have been working on getting my head around best practices for structuring my web applications. One of the strongest messages that has come through is to use dependency injection to facilitate unit testing.
As many others have noted, dependency injection is a very simple concept. If one thing (the “consumer”) depends upon something else (the “dependency”) to do its work, then the consumer should be given the dependency. The consumer should depend upon an agreed set of functionality that the dependency performs, but it should not care how the dependency actually gets the job done.
An easy example
A radio receives transmissions and plays sounds through its speakers. Nothing special there. But it needs a power source to function. The power source’s adapter has to fit into the hole on back of the radio. And there has to be some compatibility of voltages and currents and all that electrical magic. The radio is the consumer and the power source is the dependency. You can’t run the radio without the power source.
Note that, in principle, the power source could be a pack of AA batteries. Or it could be a solar cell. Or it could be a collection of gerbils running on flywheels. As long as it outputs the right voltage and current and the plug fits into the hole, then all is cool. The radio is not concerned by the power source implementation as long it lives up to what we expect a power source to do (i.e., its interface).
How about some code?
Using our radio and power source example, we could imagine PHP objects and interfaces as follows:
interface PowerSourceInterface
{
public function getPower($numberOfPowerUnits);
}
class PowerSourceGerbils implements PowerSourceInterface
{
protected $_gerbils;
public function __construct($gerbils)
{
$this->_gerbils = $gerbils;
}
pubic function getPower($numberOfPowerUnits)
{
// Make the gerbils run like hell on the flywheel
// to generate the requested amount of power.
}
}
class Radio
{
protected $_powerSource;
public function __construct(PowerSourceInterface $powerSource)
{
$this->_powerSource = $powerSource;
}
public function play()
{
// Call upon $this->_powerSource->getPower()
// Feed that power into the various electrical
// components of the radio and push the sound
// out through the speakers.
}
}
The idea is that by clearly defining the responsibilities, we can create independent object that fulfill those responsibilities. Even better, by defining interfaces, we can set up a contract for what a particular consumer expects his dependencies to be able to do for him.
So, who cares? Unit testing cares.
Suppose you are a company that makes radios. You hire the best radio engineers and work out all the details for what makes a radio great. Maybe you have amazing noise-reduction algorithms that filter out static. Maybe your radios have balance and equalizer controls to allow the user to tweak the sound he gets out of your box. Maybe your radios are super efficient so that they pull less power from the power source, allowing them to play longer before recharge. Or maybe you simply put them in colorful plastic shells featuring photos of some random pop star.
The point is that your expertise is in the areas highlighted above. You make no representation to be experts in power generation; you are a power consumer. The quality of your work can only be fairly judged when the power source functions properly. Hey, man, if the power source tanks, then all bets are off, right?
So as part of your manufacturing process, you do a whole bunch of unit-testing on your radios using a “mock” power source, a power source that definitely functions correctly. This approach – explicitly defining and passing dependencies, combined with supplying mock dependencies during unit testing – clearly defines responsibilities and allows you to focus on fulfilling yours without worrying that the problem lies in someone else’s area of responsibility.
What’s the downside?
Well, creating objects is now a bit of a pain the neck. Consider what is required now just to play a radio.
$gerbils = new GerbilCollection(); // guess we need a Gerbil class and a GerbilCollection class, too $powerSource = new PowerSourceGerbils($gerbils); $radio = new Radio($powerSource); $radio->play();
So, every time I want to play a radio, I need to construct a power source (which might have its own dependencies, like the gerbils in this case), feed that power source into the radio, and then hit the play button.
I gotta do all these steps just to play a freaking radio? Can’t I do the radio setup once – create and plug in the power source – and then when I want to listen to some music, all I have to do is hit play?
I will address this in the next post: Poor Man’s Dependency Injection using Zend Framework.
PowerSourceGerbils made me laugh
I think rather than throwing an exception in your constructor, it would be better to use type-hinting in the parameter:
__construct(PowerSourceInterface $powerSource)
@Jani: Thanks for the visit and for the comment.
Type hinting the parameter certainly makes the code clearer and helps with IDE code completion. So, it seems a good practice in general.
But actually catching the error depends on having an error handler in place, right? In the context of a Zend Framework application, we typically only have an exception handler, don’t we?
Sure, we could create an error handler that converts catchable errors to exceptions (example).
In a ZF app, how would you implement that? Just like the standard ZF ErrorHandler plugin (which strikes me as somewhat poorly named since it only catches exception)? Or are you aware of any off-the-shelf plugins that do it?
Again, thanks for the visit and the comment.
Never really thought about it that way
I think it would generally be a programming error (and rather fatal) if incorrect arg types were passed so it wouldn’t necessarily need handling as such, but yeah I guess you could have an error handler for converting them. Haven’t really seen anyone do that though.
I guess you’re right. It is more of a programming error than a runtime “bad-data” error. I’ll modify the post to reflect the better practice. Thanks and cheers!
Discussed this shortly on #zftalk-community on freenode, and decided to whip something together:
https://gist.github.com/1050932
Right, pretty similar to this approach, though your’s is cleverly targeted towards the type of error we are talking about. Cool.
Hi,
great article. I just would like to comment on “What’s the downside?
Well, creating objects is now a bit of a pain the neck. Consider what is required now just to play a radio”.
We are using DI now for some time and I must say it brought a lot of clarity in a project, but also I bit of configuration maintenance. Our code is being run in 4 installations and only some of those are asynchronously integrated with CRM. Now using DI Container we can configure the behaviour of some services across the whole installation and the domain code is completely unaware of which installation is being run, because it depends on injected collaborator. Each installation has its own DIC configuration.
Try to have a look at http://components.symfony-project.org/dependency-injection/
It is easy to configure, cachable – great piece of software.
Hi Tomas,
Thanks for the visit and the compliment on the piece.
Indeed, a Dependency Injection Container (DIC) – in whatever form that takes – is clearly the way to go. I have looked briefly at the Symfony DIC and have heard great things about it; with your recommendation, it clearly warrants more attention.
My next post – whenever I get to it – will show a kind of Poor Man’s DIC, essentially a little more than a factory. It will clearly not be as flexible or as powerful as a full-featured DIC like the one from Symfony, but hopefully it represents a step up for a novice/intermediate developer towards improved understanding of what benefits we are trying to realize by using a full-featured DIC.
Thanks and cheers!
[...] are many resources on dependency injection – including a previous blog post – but the upshot is that if an object (the consumer) needs another object (the dependency) to [...]