3

Hidden dependencies:

function __construct($dep_registry){
  $this->db = $dep_registry->get('db');
  $this->request = $dep_registry->get('request');
  ...
}

Not so hidden:

function __construct(Db $db, Request $request, ....){
  $this->db = $db;
  $this->request= $request;
  ...
}

The disadvantages of the last is that, if I need to change the class later and need another dependency, I have to change the constructor arguments. And if I change the constructor, I need to also change every file that uses it. With hidden deps the change would only be needed in the two places: the constructor and the registry class. Another fail of the 2nd method is that it's not possible to lazy load dependencies, i always have to pass the instances

So what's the disadvantage of hidden deps and why is it bigger than this disadvantage of not so hidden deps?

If you are using true Inversion of Control, then you shouldn't need to. If you need to add another dependency to the class, you should look back at what this class is doing. Perhaps you need a sub type instead. Adding dependencies could be an indication that you are breaking the Open-Closed Principal of programming (Open for extension, but closed for Modification).

OK, dependencies should not be hidden was/is a principle too. Principles are all cute and all, but what do they solve in reality, besides not violating other principles? So why is breaking the Open-Closed principle bad (assuming that this is what adding another argument to the constructor does)?

2
  • Lazy loading in the second example requires the implementation of a service locator or a DI container. A factory pattern would even work. Mar 26 '15 at 19:10
  • I like your example. I capture it in the personal rule of thumb "avoid broad parameters". Ideally only pass primitive types, or collections (in java-speak). Oct 5 '17 at 18:45
5

As an outsider looking at this code, it is not clear how the "hidden dependencies" get created to begin with. What if there is no "db" dependency registered? Does it throw an exception, return null? If my class doesn't get a "db" dependency, how bad is that?

Many times people lean towards this pattern when there are too many dependencies to pass in the constructor without making the constructor look like complete garbage. If your class requires a large number of dependencies, consider using "setter injection" instead (for a property named "foo", call "setFoo(...)" to inject that dependency). Reserve constructor arguments for those dependencies that are absolutely required just to have the object exist -- not to use the object, but just to make it exist.

It also raises the question, is this class doing too much? Do you really need some of these dependencies injected at all? There is a balance between dependency injection and coupling. Too far either way and you create headaches for yourself.

Fine, but that still doesn't answer the question. Consider my class only has 1 argument, and on month later I need to add the 2nd one. So I still need to update the 346253453245 files that use my class, right?

If you are using true Inversion of Control, then you shouldn't need to. If you need to add another dependency to the class, you should look back at what this class is doing. Perhaps you need a sub type instead. Adding dependencies could be an indication that you are breaking the Open-Closed Principal of programming (Open for extension, but closed for Modification).

It's not to say you can't modify a class once it is written, but you should not modify the intended use of that class -- it's purpose.

Adding a new dependency tells me the purpose of this class has changed, and it's likely you need to break the class up into other classes, or create a child class extending your original one. That way existing code that does not need the functionality of the new class (and the new dependency) doesn't have to change.

8
  • 5
    If a constructor has too many arguments, the class likely needs to be split into multiple classes.
    – user22815
    Mar 26 '15 at 17:19
  • Fine, but that still doesn't answer the question. Consider my class only has 1 argument, and on month later I need to add the 2nd one. So I still need to update the 346253453245 files that use my class, right?
    – Elfy
    Mar 26 '15 at 17:22
  • @Snowman: I agree. You have to examine the purpose of a class. Perhaps you only need a database context, the HTTP request, and a few service classes. Then again, once you have the database context and the request you need to do something specific. Why not instantiate the service class outright using the new operator instead of relying on the outside world to give you the service. You know what you want to do, so get down to doing it. :) Mar 26 '15 at 17:22
  • 4
    @Elfy Your 346253453245 files that use your class shouldn't have to be updated unless you're instantiating a new instance of your class in each of those places. Which, if you've designed your system with IoC in mind, you won't be. The number of files that have to call that constructor you just updated should be minimal... Hopefully there's just one (configurable) place, but certainly not in each of the 346253453245 consumers.
    – Eric King
    Mar 26 '15 at 18:08
  • 1
    @Sridhar-Sarnobat: Looking back at my answer 2 years later I agree. Avoid setter injection and go with the builder pattern. I should edit my answer to that affect. Oct 5 '17 at 23:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.