Firstly, I want to restrict this question to web development only. So this is language agnostic as long as the language is being used for web development. Personally, I am coming at this from a background in PHP.
Often we need to use an object from multiple scopes. For example, we might need to use a database class in the normal scope but then also from a controller class. If we create the database object in normal scope then we cannot access it from inside the controller class. We wish to avoid creating two database objects in different scopes and so need a way of reusing the database class regardless of scope. In order to do so, we have two options:
The problem becomes more complex when there are many classes involved all demanding objects in many different scopes. In both solutions, this becomes problematic because if we make each one of our objects global, we are putting too much noise into the global scope and if we pass too many parameters into a class, the class becomes much more difficult to manage.
Therefore, in both cases, you often see the use of a registry. In the global case, we have a registry object which is made global and then add all of our objects and variables to that making them available in any object but only putting a single variable, the registry, into the global scope. In the DI case, we pass the registry object into each class reducing the number of parameters to 1.
Personally, I use the latter approach because of the many articles that advocate it over using globals but I have encountered two problems. Firstly, the registry class will contain huge amounts of recursion. For example, the registry class will contain database login variables needed by the database class. Therefore, we need to inject the registry class into the database. However, the database will be needed by many other classes and so the database will need to be added to the registry, created a loop. Can modern languages handle this okay or is this causing huge performance issues? Notice that the global registry does not suffer from this as it is not passed into anything.
Secondly, I will start passing large amounts of data to objects that don't need it. My database doesn't care about my router but the router will get passed to the database along with the database connection details. This is made worse through the recursion problem because if the router has the registry, the registry has the database and the registry and the registry is passed to the database, then the database is getting passed to itself via the router (i.e. I could do $this->registry->router->registry->database
from inside the database class`).
Furthermore, I don't see what the DI is giving me other than more complexity. I have to pass an extra variable into each object and I have to use registry objects with $this->registry->object->method()
instead of $registry->object->method()
. Now this obviously isn't a massive problem but it does seem needless if it is not giving me anything over the global approach.
Obviously, these problems don't exist when I use DI without a registry but then I have to pass every object 'manually', resulting in class constructors with a ridiculous number of parameters.
Given these issues with both versions of DI, isn't a global registry superior? What am I losing by using a global registry over DI?
One thing that is often mentioned when discussing DI vs Globals is that globals inhibit your ability to test your program properly. How exactly do globals prevent me from testing a program where DI would not? I have read in many places that this is due to the fact that a global can be altered from anywhere and thus is difficult to mock. However, it seems to me that since, at least in PHP, objects are passed by reference, changing an injected object in some class will also change it in any other class into which it has been injected.
Constructor-based dependency injection is certainly considered a best practice. There was a time I personally favored setter-based injection, but I have come around to constructor-based. We can still improve our example.
The most common way is using constructor injection, which requires that all software dependencies be provided when an object is first created. However, constructor injection makes the assumption that the entire system is using the pattern, which means the entire system must be refactored at the same time.
An alternative to dependency injection is using a service locator. The service locator design pattern also improves decoupling of classes from concrete dependencies. You create a class known as the service locator that creates and stores dependencies and then provides those dependencies on demand.
There are three types of dependency injection — constructor injection, method injection, and property injection.
Let's tackle this one by one.
Firstly, the registry class will contain huge amounts of recursion
You do not have to inject the Registry class into the database class. You can just as well have dedicated methods on the Registry to create the required classes for you. Or if you inject the Registry, you can simply not store it but only grab from it what is needed for the class to be instantiated properly. No Recursion.
Notice that the global registry does not suffer from this as it is not passed into anything.
There might be no recursion for the Registry itself, but objects in the Registry may very well have circular references. This could potentially lead to memory leaks when unsetting objects from the Registry with PHP Versions before 5.3 when the Garbage Collector would not collect those properly.
Secondly, I will start passing large amounts of data to objects that don't need it. My database doesn't care about my router but the router will get passed to the database along with the database connection details.
True. But that's what the Registry is for. It's not much different from passing $_GLOBALS into your objects. If you dont want that, dont use a Registry, but only pass in the arguments required for the class instances to be in a valid state. Or simply dont store it.
I could do $this->registry->router->registry->database
It is s unlikely that router exposes a public method to get the Registry. You wont be able to get to database
from $this
through router
, but you will be able to get to database
directly. Certainly. It's a Registry. That's what you wrote it for. If you want to store the Registry in your objects, you can wrap them into a Segregated Interface that only allows access to a subset of the data contained within.
Obviously, these problems don't exist when I use DI without a registry but then I have to pass every object 'manually', resulting in class constructors with a ridiculous number of parameters.
Not necessarily. When using constructor injection, you can limit the number of arguments to those absolutely necessary to put the object into a valid state. The remaining optional dependencies can very much be set through setter injection as well. Also, no one hinders you to add the arguments in an Array or Config object. Or use Builders.
Given these issues with both versions of DI, isn't a global registry superior? What am I losing by using a global registry over DI?
When you use a global Registry you are tight coupling this dependency to the class. This means the using classes cannot be used without this concrete Registry class anymore. You assume there will be only this Registry and not a different implementation. When injecting the dependecies, you are free to inject whatever fulfills the responsibility of the dependency.
One thing that is often mentioned when discussing DI vs Globals is that globals inhibit your ability to test your program properly. How exactly do globals prevent me from testing a program where DI would not?
They do not prevent you from testing the code. They just make it harder. When Unit-Testing you want to have the system in a known and reproducable state. If your code has dependencies on the global state, you have to create this state on each test run.
I have read in many places that this is due to the fact that a global can be altered from anywhere and thus is difficult to mock
Correct, if one test changes the global state, it might affect the next tests if you do not change it back. This means you have to take effort to recreate the environment in addition to setting your Subject-Under-Test into a known state. This might be easy if there is just one dependency, but what if there is many and those depend on the global state too. You'll end up in Dependency Hell.
I'll post this as an answer since I'd like to include the code.
I've benchmarked passing an object versus using global
. I basically created a relatively simple object, but one with a self reference and a nested object.
The results:
Passed Completed in 0.19198203086853 Seconds
Globaled Completed in 0.20970106124878 Seconds
And the results are identical if I remove the nested object and the self reference...
So yes, it appears that there's no real performance difference between these two different methods of passing data. So make the better architectural choice (IMHO that's the Dependency Injection)...
The script:
$its = 10000;
$bar = new stdclass();
$bar->foo = 'bar';
$bar->bar = $bar;
$bar->baz = new StdClass();
$bar->baz->ar = 'bart';
$s = microtime(true);
for ($i=0;$i<$its;$i++) passed($bar);
$e = microtime(true);
echo "Passed Completed in ".($e - $s) ." Seconds\n";
$s = microtime(true);
for ($i=0;$i<$its;$i++) globaled();
$e = microtime(true);
echo "Globaled Completed in ".($e - $s) ." Seconds\n";
function passed($bar) {
is_object($bar);
}
function globaled() {
global $bar;
is_object($bar);
}
Tested on 5.3.2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With