Hacker News new | ask | show | jobs
by kgeist 1462 days ago
You're doing it wrong: you pass LoggerSocket as a function argument. You're trying to use it in a very procedural way: you create a logger of a concrete class, you pass it to a function which expects a concrete class, you call a function on it. Of course it will be the same, and we have none of the benefits of OOP in this example. In a real project, the logger will already be preconstructed somewhere else (DI framework, for example, but manual initialization somewhere at startup works as well) and stored as an object field of the current class (in a functional language that would be a captured value). So at most of the call sites it's going to be just this.logger.log("some error happened") without any mention of sockets or any other kind of boilerplate/implementation details. Our client class has a single responsibility by delegating additional work to a dependency defined as a simple protocol ("interface") in its constructor, which is useful for readability and modularity (you can inject anything you want, provided it implements the protocol), and in case implementation details of the dependency change, we don't have to modify every call site. Another thing is that your code is not testable because you can't swap the socket with a mock (at least, in languages with strong/static typing). You can implement all this with structs and function pointers in something like C, the difference here is that object-oriented languages have nice syntax sugar for it.
1 comments

>You're doing it wrong: you pass LoggerSocket as a function argument.

This is because I'm too lazy to write out a full class definition for DI. The intent of my example still stands. I'm writing a lot, and a lot of examples too, so forgive me if I don't write out the entire DI pattern of passing ownership of the Dependency to a class.

>In a real project, the logger will already be preconstructed somewhere else (DI framework, for example, but manual initialization somewhere at startup works as well) and stored as an object field of the current class (in a functional language that would be a captured value).

Bro, just pretend that the doStuff function is a constructor and that it also happens to do more stuff then I wrote, like maybe assign it to a member variable if that helps you understand better.

>So at most of the call sites it's going to be just this.logger.log("some error happened") without any mention of sockets or any other kind of boilerplate/implementation details.

I would say the logger instance is a boiler plate detail. It's a wrapper around all the dependencies. WrapperSocket/LoggerSocket etc. Ideally what we want is just log(string). Too bad.

>Our client class has a single responsibility by delegating additional work to a dependency defined as a simple protocol ("interface") in its constructor, which is useful for readability and modularity (you can inject anything you want, provided it implements the protocol),

The client class has more then one responsibility. It has to log something AND manage dependencies. That's two responsibilities. You can argue for the interpretation of responsbilities, but think about it like this. Compared with a stateless function that doesn't MANAGE ANY STATE, a stateless function is even more "single" in terms of responsibility.

I'm going to stop responding to this thread here. Most of the points are in the other thread, pointless to have two. Just go back to the other thread, state your points, and I will respond.