Tony Marston's Blog About software development, PHP and OOP

The true purpose of Dependency Injection

Posted on 28th November 2018 by Tony Marston
Introduction
Dependency Injection is useless without Polymorphism
A new method of injecting the dependency
Misleading definitions of Dependency Injection
The true purpose of Dependency Injection
Conclusion
References
Comments

Introduction

Ask any programmer to describe the purpose of Dependency Injection (DI) and they will probably echo one of the following:

In software engineering, dependency injection is a programming technique in which an object or function receives other objects or functions that it requires, as opposed to creating them internally. Dependency injection aims to separate the concerns of constructing objects and using them, leading to loosely coupled programs.
Dependency injection (DI) is a programming technique that separates the construction of objects from their use. Objects get the objects they need from an outside source instead of creating them themselves.
Dependency injection is a technique used in object-oriented programming (OOP) to reduce the hard-coded dependencies between objects.
Dependency injection is a programming technique that makes a class independent of its dependencies. It achieves that by decoupling the usage of an object from its creation.

This is not correct. This is a description of how it does it, but not why it does it. The true purpose of DI is to provide a means to take advantage of polymorphism where multiple objects share the same method signature(s) but with different implementations. This allows another object to call one of those methods without knowing the identity of that dependent object and without knowing its implementation details. Instead of being tightly coupled to a single dependent object it is loosely coupled to a collection of interchangeable objects. The identity of the dependent object is chosen outside of the calling object and then injected into it, and that object then calls that method on whatever dependent object it has been given. In this way the calling object can be reused with a variety of different dependent objects.

Dependency Injection is useless without Polymorphism

Although I have been surfing the interweb thingy for several decades looking for useful articles on programming in general and OOP in particular I have never come across any articles which state that dependency injection and polymorphism go hand-in-hand. This is a glaring omission and just proves that the authors of those documents which fail to make this connection do not fully understand its significance. Since a major motivation for object-oriented programming is software reuse it is vitally important to show how these two supposedly independent topics, when combined, can provide reusable software.

As an example let's say that we have an object A which calls a method M which exists in several classes C1 to C99. This means that object A can call method M on any one of the 99 versions of class C. It would be very wasteful to have those 99 calls hard-coded, so by using DI you can code the call just once on an object which is chosen outside of object A. This also means that you can create even more versions of object C without having to make any modifications to object A.

Here is a true example from my own codebase which can be seen in my Sample Application which I first released in 2003. In my implementation of the Model-View-Controller design pattern I have a series of reusable Controllers which can operate on any Model in the application. This is made possible by the fact that every Model (concrete table class) inherits from the same abstract table class which provides a set of common table methods. As these methods are available in every table class this provides the polymorphism. When I built my first Controller I used code similar to the following:

<?php 
require_once 'classes/person.class.inc';  // a hard-coded reference
$dbobject = new Person;                   // a hard-coded reference
$result = $dbobject->updateRecord($_POST);
if ($dbobject->errors) {
    // do error handling 
}
?> 

The above is an example of tight coupling as the identity of the class which is to be instantiated is hard-coded. This would mean that every table class would require its own version of that Controller. This demonstrates zero reusability. The following example totally eliminates this tight coupling:

-- in a component script
$table_id = 'person'>;      // identify table
require 'std.update1.inc';  // activate controller

-- in a controller script std.update1.inc
<?php 
require_once 'classes/$table_id.class.inc';  // $table_id is provided by the previous script
$dbobject = new $table_id;                   // NOT a hard-coded reference
$result = $dbobject->updateRecord($_POST);
if ($dbobject->errors) {
    // do error handling 
}
?> 

In the above example I have a separate component script for each user transaction (use case), but I can reuse that particular controller script as many times as I like.

A new method of injecting the dependency

When I was first told by my critics that my code was considered to be crap because I wasn't using DI I looked it up on the interweb thingy and didn't like what I saw. I also did not see any similarity in my codebase to the methods of injection, so I naturally assumed that by not following those instructions to the letter that I was not implementing DI at all.

It was not until many years later that I realised that I was actually using DI, but with a new and different method of injection. Instead of injecting an object I was injecting nothing but the name of the class which was then instantiated within the object which called the method(s) on that dependent object. This was made possible by the fact that every one of my concrete table classes only has a single configuration, and that configuration is carried out within the class constructor.

This is another example of where I choose to do things differently and achieve superior results, yet my critics can do nothing but complain that my implementation must surely be inferior simply because it is different.

Note also that PHP allows me to instantiate a class whose name is contained within a variable and not hard-coded. This may not be possible with other less flexible languages.

Misleading definitions of Dependency Injection

By constantly echoing these misleading definitions of DI these authors are both showing their ignorance and polluting the pool of knowledge regarding OOP. Instead of providing clarity they are sowing the seeds of confusion. These "wrong" definitions are as follows:

Dependency injection aims to separate the concerns of constructing objects and using them..

The principle of Separation of Concerns (SOC), which is exactly the same as the Single Responsibility principle (SRP), is not concerned with trivialities such separating 2 lines of code such as the following:

$object = singleton::getInstance('foobar');
$result = $object->doStuff($arg);

Splitting these two lines of code apart just to satisfy the rule you must separate the construction of objects from their use does not work for me. What good thing happens if I follow this rule? What bad thing happens if I don't? Does it make the code more readable? Does it make the code run faster? If a rule cannot be justified then it should not exist.

SRP and SOC are concerned with taking large monolithic programs which contain large volumes of code and splitting them into cohesive units such as those shown in the 3-Tier Architecture or the Model-View-Controller design pattern.

DI leads to loosely coupled programs

This shows a complete lack of understanding on the topic of coupling. This describes how modules interact and has absolutely nothing to do with how or where the dependent module is instantiated. Where there is a call from module A to module B then those two modules are coupled. If there is no call then those two modules are not coupled. The degree of coupling - either high/tight or low/loose - depends entirely on the absence or presence of the ripple effect when changes in one module force changes in other modules.

DI is a technique to reduce the hard-coded dependencies between objects.

Why exactly is having hard coded dependencies a problem? As far as I can see the only problem arises when a dependency can be switched between a number of alternatives and the use of each alternative would otherwise duplicated code which would mean violating the DRY principle. I avoid this particular problem using the example shown above.

As explained in Object Types I recognise only two types of object - entities and services - and while I am happy to inject entities into services I never inject entities into entities for the simple reason that there is never an alternative to that dependent entity so I don't wast my time in coding for a situation that will never exist.

DI makes a class independent of its dependencies.

What is the value in this? In my large enterprise application I have many instances where a user transaction is required to access several database tables, so while that transaction starts by going into a main entity it has to access dependent entities in order to complete its task. The method calls to these dependent objects are hard-coded in that main entity, so they cannot be made outside of that main entity and therefore cannot be independent from that main entity. In this case none of those dependent entities has an alternative, so using DI would be a waste of time. In such cases the identity of the dependent object as well as the method call is hard-coded and this never causes a problem. If it doesn't cause a problem then in my universe it doesn't need a solution, especially a solution which takes working code and splits it into fragments.

It achieves that by decoupling the usage of an object from its creation.

This statement is pure rubbish. The use of DI does not decouple an object from its dependencies. Coupling exists whenever a module makes a call to another module. The only way to remove the coupling is to remove the call, in which case there would be no dependency and therefore no need for dependency injection.

The true purpose of Dependency Injection

The true purpose of DI is therefore not to separate a dependent object's instantiation from its use, but to provide a mechanism which allows the dependent object to be switched to one of a number of alternatives which share the same method signature. It should therefore follow that if a particular dependency does not have any alternatives then providing a mechanism to switch between alternatives which don't exist would be a complete waste of time and a violation of YAGNI.

Here is my own personal definition of Dependency Injection:

Conclusion

All these incorrect and inadequate descriptions of Dependency Injection made me believe that it was not worth the effort of adding it to my codebase. When I realised the true purpose of DI I also realised that it had been included in the code which I wrote way back in 2003 and which still provides benefits in my current codebase. I missed it because the description of my implementation did not match any of the descriptions which I read.

This just goes to show that badly written principles have no value, and by implementing a badly written principle you are unlikely to add value to your code.

Here endeth the lesson. Don't applaud, just throw money.


References

These are reasons why I consider some ideas on how to do OOP "properly" to be complete rubbish:


counter