Hacker News new | ask | show | jobs
by Kinrany 29 days ago
I dislike OOP as much as the next HN commenter, but dependency injection tools are good in principle. OOP just uses them much more and for bad reasons.
2 comments

Dependency Injection frameworks are a workaround for limitations of the paradigm, much like Design Patterns also are. They're necessary (by a very stretched definition of necessary) because people want to do certain things and the paradigm is fighting them.

Some part of the code wants to do logging. The paradigm-native solution is to just instantiate a logger object directly, but that's not really what you want to happen, because there are application-level concerns as to where those logging messages should go.

So instead of a direct new, you create a Logger interface (extra work), then use the Factory pattern (extra work) to hide the concrete logger objects that get instantiated. Ok, but now you are still creating multiple independent loggers, which will trample on each other.

So you employ the Singleton pattern (extra work), but now you can't use different loggers for different parts of the codebase.

Ok, let's instead have the application instantiate the proper loggers, and then thread them through method arguments as needed (extra work).

But now passing all those loggers around is super annoying and extremely noisy, so you create a framework that can inject them where they are needed.

Great, now you have a whole framework just to pass some logger objects around. The framework is not the problem per se, it exists for a reason, the problem is that someone felt the need for it. And it is a massive pile of complexity that is NOT worth it.

Ignoring the singleton for now, because I don't quite see the need.

What's the alternative? Which part of this is actually caused by OOP? If you want to isolate IO, which you should, then you need to inject things that do IO and you need to specify a contract for them. And then it's nice to instantiate things or call functions without explicitly instantiating the whole tree of their dependencies.

There are various ways to solve the problem (if you consider it a problem, I personally don't, there's no need for things to be this extremely decoupled, but for the sake of argument):

1) Allow objects to send messages to an implicit "parent" object, which supports "doesNotUnderstand" ala Smalltalk and that by default also forwards to the implicit parent. That means you can implement "log(...)" at the right point in the abstraction without manually passing a logger around.

2) Use algebraic effects, no need to handle this with OOP. A function requests the "log" effect and a handler defined in a higher level scope takes care of implementing log.

3) Have a language with an implicit context parameter that allows putting arbitrary things in it (closest would be Odin but Odin has a fixed set of operations in the context). Because it is implicit in the calling convention methods can be oblivious to it.

4) You separate the concerns: logging is the act of recording some information. Where that information is ultimately sent or stored is a separate concern. This is the more functional view of the problem. If you follow a "model-view-update" approach like the so called "Elm Architecture", logging is just another task produced by the view function, it doesn't need any special handling.

It's not that this is a "problem" of OOP, it is that OOP doesn't give you any tools for tackling this problem. That is why dependency injection frameworks exist. Some other paradigms and non-OOP architectures don't have any trouble with it. You can also replicate those architectures in an OOP language, but that's still "contorting the paradigm" rather than it helping or guiding you towards a clean solution.

Personally I think a global singleton logger is perfectly reasonable, it is other people (the ones using DI frameworks) that think otherwise ;)

OOP isn't why they are used. OOP may enable injection more than others, but it is not encouraged. Bad programmers and bad architects is why it is overused not OOP. Keep the blame in the right place.