Iâll try to exemplify my concerns with real experiences of my own. Again, Iâm not against Dependency Injection or any other pattern. I defend the opposite of this, but always with a concern of over abusing when thereâs âno need for itâ or âforbides meâ. But letâs go to the examples:
##Pont one: too much abstraction (with some narrative for fun)##
A long long time ago⊠in a not so distant desk on an UK office, a boss asks me to implement a new Person entity and to calculate a âslugâ for it. For simplicity of the examples, Iâve implemented it in the __toString
and just concatenate everything, but let us make the abstraction for a second and imagine a real business class.
So, the first though is to do a dirty and ugly approach. Implement everything in that business class.
class Person
{
private $name;
private $dateOfBirth;
private $address;
private $portNumber;
function __construct($name, $dateOfBirth, $address, $portNumber)
{
$this->name = $name;
$this->dateOfBirth = $dateOfBirth;
$this->address = $address;
$this->portNumber = $portNumber;
}
public function __toString()
{
return sprintf("%s - %s - %s - %s",
$this->name, $this->dateOfBirth->format("Y-m-d H:i:s"), $this->address, $this->portNumber);
}
}
$p = new Person('Mr. Batman', new \DateTime("today"), 'Somewhere', '10');
echo "Assertion result => " . (assert("Mr. Batman - 2015-03-05 00:00:00 - Somewhere - 10" == $p) ? "true" : "false") . "\r\n";
Hum⊠but I thought "This has too many responsibilities and one might need to change the slug
or the Person
. But then, maybe, in the future my business class may have the need to calculate the slug in another way, and having the sprintf
hardcoded isnât good, so letâs make a dependency out of itâŠ
Ok, just need to create a new file with a formatter class, arrange it in the appropriate namespace (imagine), make some tests for it (another class) and have some getters for my members in my business class⊠no big deal⊠"
class Person
{
private $formatter;
private $name;
private $dateOfBirth;
private $address;
private $portNumber;
public function getName()
{
return $this->name;
}
public function getDateOfBirth()
{
return $this->dateOfBirth;
}
public function getAddress()
{
return $this->address;
}
public function getPortNumber()
{
return $this->portNumber;
}
function __construct($formatter, $name, $dateOfBirth, $address, $portNumber)
{
$this->formatter = $formatter;
$this->name = $name;
$this->dateOfBirth = $dateOfBirth;
$this->address = $address;
$this->portNumber = $portNumber;
}
public function __toString()
{
return $this->formatter->format($this);
}
}
class PersonFormatter
{
private $format;
public function __construct($format)
{
$this->format = $format;
}
public function format(Person $p)
{
return sprintf($this->format, $p->getName(), $p->getDateOfBirth()->format("Y-m-d H:i:s"), $p->getAddress(), $p->getPortNumber());
}
}
$pf = new PersonFormatter("%s - %s - %s - %s");
$p = new Person($pf, 'Mr. Batman', new \DateTime("today"), 'Somewhere', '10');
echo "Assertion result => " . (assert("Mr. Batman - 2015-03-05 00:00:00 - Somewhere - 10" == $p) ? "true" : "false") . "\r\n";
"Yes. Much better, now I have a Person
entity and I can replace my slugger at any time!
Hum⊠wait! I really donât like that Iâm stuck with formatting the dates with my localeâs machine. Probably in the future this will be deployed in a Portuguese computer and the system will need to compute âSegunda-feiraâ instead of âMondayâ. What I need is to have a DateFormatter
⊠And because this is easy⊠why not?
Ok, just need to create a new DateTimeFormatter
passed the locale
(imagine this), have a new bunch of tests for each of them, and change the PersonFormatter
. "
class Person
{
private $formatter;
private $name;
private $dateOfBirth;
private $address;
private $portNumber;
public function getName()
{
return $this->name;
}
public function getDateOfBirth()
{
return $this->dateOfBirth;
}
public function getAddress()
{
return $this->address;
}
public function getPortNumber()
{
return $this->portNumber;
}
function __construct($formatter, $name, $dateOfBirth, $address, $portNumber)
{
$this->formatter = $formatter;
$this->name = $name;
$this->dateOfBirth = $dateOfBirth;
$this->address = $address;
$this->portNumber = $portNumber;
}
public function __toString()
{
return $this->formatter->format($this);
}
}
class DateTimeFormatter {
private $format;
public function __construct($format) {
$this->format = $format;
}
public function format(\DateTime $theDate) {
return $theDate->format($this->format);
}
}
class PersonFormatter
{
private $format;
private $dateTimeFormatter;
public function __construct(DateTimeFormatter $dateTimeFormatter, $format)
{
$this->dateTimeFormatter = $dateTimeFormatter;
$this->format = $format;
}
public function format(Person $p)
{
return sprintf($this->format, $p->getName(), $this->dateTimeFormatter->format($p->getDateOfBirth()), $p->getAddress(), $p->getPortNumber());
}
}
$dtf = new DateTimeFormatter("Y-m-d H:i:s");
$pf = new PersonFormatter($dtf, "%s - %s - %s - %s");
$p = new Person($pf, 'Mr. Batman', new \DateTime("today"), 'Somewhere', '10');
echo "Assertion result => " . (assert("Mr. Batman - 2015-03-05 00:00:00 - Somewhere - 10" == $p) ? "true" : "false") . "\r\n";
"Cool! Didnât even need to change the `Person`! Hurray, itâs decoupled! But⊠the `Personâs` entity name in the future could be composed by a title, name and surname. Maybe what I need is to replace with yet another dependency. Just need to create a few more classes, have proper getters, a few more tests, change the `Person` constructor et voilĂĄ."
class PersonIdentification {
protected $name;
public function getName() {return $this->name; }
public function __construct($name) {
$this->name = $name;
}
public function __toString() {
return $this->name;
}
}
class Birthday {
private $formatter;
private $dateOfBirth;
public function getDateOfBirth() { return $this->dateOfBirth; }
public function __construct(DateTimeFormatter $formatter, \DateTime $dateOfBirth) {
$this->dateOfBirth = $dateOfBirth;
$this->formatter = $formatter;
}
public function __toString() {
return $this->formatter->format($this->dateOfBirth);
}
}
class Location {
private $address;
private $portNumber;
public function getAddress() { return $this->address; }
public function getPortNumber() { return $this->portNumber; }
public function __construct($address, $portNumber) {
$this->address = $address;
$this->portNumber = $portNumber;
}
public function __toString() {
return $this->address . " - " . $this->portNumber;
}
}
class Person
{
private $formatter;
private $personIdentification;
private $birthday;
private $location;
public function getPersonIdentification() { return $this->personIdentification; }
public function getBirthday() { return $this->birthday; }
public function getLocation() { return $this->location; }
public function __construct($formatter, PersonIdentification $personIdentification, Birthday $birthday, Location $location) {
$this->formatter = $formatter;
$this->personIdentification = $personIdentification;
$this->birthday = $birthday;
$this->location = $location;
}
public function __toString() {
return $this->formatter->format($this);
}
}
class DateTimeFormatter {
private $format;
public function __construct($format) {
$this->format = $format;
}
public function format(\DateTime $theDate) {
return $theDate->format($this->format);
}
}
class PersonFormatter
{
private $format;
public function __construct($format)
{
$this->format = $format;
}
public function format(Person $p)
{
return sprintf($this->format, $p->getPersonIdentification(), $p->getBirthday(), $p->getLocation());
}
}
$dtf = new DateTimeFormatter("Y-m-d H:i:s");
$pf = new PersonFormatter("%s - %s - %s");
$b = new Birthday($dtf, new \DateTime("today"));
$pi = new PersonIdentification('Mr. Batman');
$l = new Location('Somewhere', '10');
$p = new Person($pf, $pi, $b, $l);
echo "Assertion result => " . (assert("Mr. Batman - 2015-03-05 00:00:00 - Somewhere - 10" == $p) ? "true" : "false") . "\r\n";
âWhat ifâŠâŠâ and then my boss arrives and asks me⊠âHave you finished? Why did you took 10 hours do a simple Persons class and a slugger.?" And I answer, âYes, it took me a while, but my implementation is ready to be deployed in a new country and to calculate the slug in any way you would like!â The boss answer âWhat!? Did you know that this âlibertyâ costs me 400$ of your time!?â. You know what happens nextâŠ
Besides the narrative, my point is, you have to know where to stop. In the current context I would probably stop at the second stage. Because that was the perceive limit of the value of the next line of code, beyond that for the companies profile, it was just âgarbageâ. If, on the other hand, the company produces software to other countries, than I would probably proceed one step further.
I must know that each line of code is going to brings value to the customer, as he might not understand what âtomorrowâ is (there is a point for this at the bottom), even if I demonstrate it with previously examples. Otherwise, youâll make a dependency out a int
because it could turn out to be a 256 bit number in the future. And yes, Iâve seen this happening on a subcontract application!
##Seconds: Readability##
Either you be an expert or a junior I think that thereâs no question that the first example above is easier to read than the second. All my business and context is there, in one file, in one class. The question is, if Iâm the guy that is in a hurry to fix a bug because the functionality must be online in about 2 minutes, and someone forgot to write a test for it, then, in this scenario, if I was the wrench guy, would prefer the first one.
If the program was just handled to me, without any context, because the guy was fired, and I needed to evaluate the flow of a program, again, the first one is easier to read, because Iâve got all the context of it.
Again, Iâm not defending, in this context, the first dirty solution, just showing some of the advantages of it. Will they overcome every other benefits? That will depend on a number of factors that might even include the history and pattern of implementation of the application itself.
##Third: Resources (blame DI Containers!)##
As someone mention before, DI can have some minor performance penalty, and Iâll try to demonstrate with an example from your own research.
Dependency Injection is almost associated with a container and there quite a few plenty of container to choose from. Each has some advantages and disadvantages and its own learning curve. Now, as you mentioned in the article http://www.sitepoint.com/php-dependency-injection-container-performance-benchmarks/, depending of the container youâll have a dependency resolution time and memory profile, which might be a concern. For instance, if Iâm on a cloud I want to minimize all of these to save me costs. Of course, if you really want performance and control, you can build your own DI Container, or choose another language like C or C++, but even in PHP I could be using HHVM and have that performance boost. And having to know another language just because of it could be a wrong move.
The problem is not the DI by it self, but the chosen DI container.
Forth: Dependency Resolution
Youâve mentioned before that you postpone every single dependency of your classes to the entry point of the application. Let me take you to a simple example. Iâve create a simple REST project base on Standard Symfony distribution with 7 external bundles and Iâve got three more bundles of my own (JMSSerializerBundle, FOSUserBundle, DoctrineFixturesBundle, FOSOAuthServerBundle, FOSRestBundle, NelmioCorsBundle, StofDoctrineExtensionsBundle)
Roughly there are 372 exposed services, with, I donât even what to calculate, thousands of constructor parameters. And those are the exposed services, because there plenty more classes instantiated in each module. Can you imagine if no bundle gave you the âservices.ymlâ and you have to setup the dependency graph on your own (with or without using a DI Container). The amount of documentation that you need to read would be tremendous, not to say that youâll loose the ability to simply do
new JMS\SerializerBundle\JMSSerializerBundle(),
to use the JMSSerializer bundle.
From what Iâve understand, is that you turn almost every class (except for those that are implementations of abstractions) into a dependency to be resolved in the entry point. For the bundles that I mentioned early, there are 12000 php files, even if there is 10% of classes/interfaces that are dependencies (and the rest of them are implementations), there would be 1200 files to graph. Maybe I just donât understand what you mean. If this isnât what you intent to say can you rephrase? If it is, how do you setup a project like this?
Having a module, predefine its own dependency resolution gives me the power of:
new JMS\SerializerBundle\JMSSerializerBundle(),
Fifth: The benefits costs
Not all customers evaluate the benefits that we programmers do. If all of these items, that you correctly mentioned, Robustness, Maintainability, usefulness, scalability, âŠ, will cost 20% more, youâll probably lose the customer. This is actually pretty easy to see the number of projects that are won, just because they are cheaper.
The sponsor might even understand those benefits on the long run, but it just might not have the extra 20% bucks. So, we are again, stuck.
Again, there are things that need to address daily, whenever I need to budget a project. I need to balance the customer needs with the resources that Iâve got available.
##Conclusion⊠for now.##
These are real examples, lived by me, shown with very simple examples Some of them are a daily obstacle others are extreme cases, but Iâve experience pretty much all of those.
Again, nothing that Iâve mention above is to be read as âgood practices are bad". But whenever we encountered an obstacle, we need to know which way is the best to address it. And, sometimes, the ugly way could be your best choice.
Thank you for your time