Hacker News new | ask | show | jobs
by vbezhenar 3926 days ago
Constructors have their problems. First: constructor calling is not readable because Java misses named parameters. It's fine when there are 2-3 dependencies, but not more. Second: circular dependency is not possible. So I prefer to use setter dependencies, as they don't have those problems.
4 comments

Constructor calling is readable either with an IDE, or using distinctive types. If the constructor is unclear, it's a smell.

I'd also say that making circular dependencies not possible is a feature, not a bug. They go against everything we've learned about computing in the last 20 years. Entire languages do not have variables, or at least heavily discourage them, and the gains outweigh the costs.

Setter dependencies have other major disadvantages, like how easy they make creating partially initialized objects: Refactor something in one place, adding a parameter, and good luck making sure that the change is made everywhere else. A constructor guarantees fully initialized objects, and that's a great things.

Java pays for the mistakes of the early 2000s: The Java Bean pattern, and all the infrastructure around it, makes Java code be full of default constructors that force us into mutable objects and nullable fields.

If there is a defense for your argument for setter dependencies, is that we cannot count on any of the nice things that come with, say a Scala case class, because so much Java code out there is built with standards from a less enlightened era that using what would be better standards in any more modern language leads to inconsistencies, due to all the ancient code using Spring, Hibernate, Guava and EJB code that is hanging out there, and that would lead to inconsistencies for those trying to make Java actually use some of its new functional programming inspired features.

> First: constructor calling is not readable because Java misses named parameters. It's fine when there are 2-3 dependencies, but not more.

If you have more than 2-3 dependencies, it might be time to refactor things, as your class may be doing too many things.

> Second: circular dependency is not possible.

Circular dependencies are possible with constructor injection when using a DI framework (e.g., using a Provider in Guice [1], or the equivalent in Dagger).

[1] https://github.com/google/guice/wiki/CyclicDependencies

I agree on the first. I'm only advocating for keeping the constructor as the DI entry point. A DI framework can then be used for the convenience they provide in wiring things up at main.

Regarding the second: I think circular dependencies should be avoided whenever possible so I view the difficulty constructors create there as advantage rather than a problem.

https://en.wikipedia.org/wiki/Circular_dependency#Problems_o...

By using a DI framework to wire up constructors you tend to use the compile-time checking that your app is wired correctly. Which is unfortunate. You only find out when you try to run it.

Perhaps things being too big to wire together manually is a smell that the application is getting rather large.

There are also other patterns that can be used to make manual dependency injection less of a pain. I wrote about some of them here

http://benjiweber.co.uk/blog/2014/09/13/frameworkless-depend...

I'd rather inject some lazy somethingsomething-provider reference wrapper to break the occasional circular dependency than work with a bunch of nonfinal references that supposedly never change, because DI, but are otherwise indistinguishable from true working state.