4 ms·
>The concept of a "logger" (defined as an interface, for example) isn't tied to any particular implementation. Imagine you have 1000 files that pass sockets aro
by deltaonezero 4y ago
>The concept of a "logger" (defined as an interface, for example) isn't tied to any particular implementation. Imagine you have 1000 files that pass sockets around (a plausible scenario because logging is essential).
Interfaces are a separate topic. In my example, I log to TWO sockets. If you want to imitate the same thing with a log manager you need TWO instances of LogManager.
If you have 1000 files you either pass the socket around to ALL 1000 files or you pass the LogManager around. Same thing. The logManager is simply a wrapper around the Socket.
>If I use an interface, I only touch the initialization code (often 1 single file) by plugging in a different implementation - all 1000 files immediately start using the new implementation with no effort.
Interfaces are a different topic for a different use case. But your point also doesn't fly with me. Because you can REDEFINE a function as well. You define the function in one file and it's used in 1000 files. You redefine it in one place it's redefined in everywhere.
>That initialization could be a dependency framework of your choice, or manual initialization.
So? I can initialize dependencies and pass it around as well. Same thing as passing a log manager everwhere.
d1 = DependencyManagerSocketFilesEtc()
d2 = DependencyManagerSocketFilesEtc()
logStuff(d1, "blah")
logStuff(d2, "blah")
versus:
l1 = LoggerWidthDependenciesFilesSocketsEtc()
l2 = LoggerWidthDependenciesFilesSocketsEtc()
l1.log("blah")
l2.log("blah")
l1, l2, d1, d2, need to be passed Everywhere your program is used. There is no getting away from this. Doesn't matter if you use OOP or not.
But you'll note one thing. d1 and d2 are reusable. and modular. I can reuse sockets and files with other things. With a logManager I don't even have access. Which is my point.
deleteContents(d1)
>If you emulate this behavior with structs, it's still OOP, just without the syntax sugar.
I am not emulating this behavior with structs. Just because they tell you in C++ 101 that a struct and a class is the same thing doesn't mean it actually is in a general sense. A struct has no methods in general. It's just raw data. A class is more of a mixture of data AND methods. OOP is the later not the former.
>In OOP, you usually don't pass dependencies around as function arguments, you inject them in constructors once somewhere at startup time and forget about it.
You aren't hearing my argument. Injecting the dependency into a class necessitates the ENTIRE class to be passed around. You either pass the dependencies or you pass around the wrapper class they are injected into. Let me give you an example with a higher level of scope.
s1 = socket()
s2 = socket()
doStuff(s: socket) {
...
logToSocket(s, "blah")
...
...
}
doStuff(s1)
doStuff(s2)
versus:
l1 = logManager()
l2 = logManager()
doStuff(logWithSocket: logManager) {
...
logWithSocket.log("blah")
...
}
doStuff(l1)
doStuff(l2)
Virtually no difference. Think of LogManager as SocketWrapper. Just putting a socket in a wrapper doesn't change the fact that you have to pass it around all over the place.
- kgeist 4y ago>If you have 1000 files you either pass the socket around to ALL 1000 files or you pass the LogManager around. Same thing. The logManager is simply a wrapper around the Socket. Not quite the same thing. If we suddenly want a file instead of a socket, we need to replace all instances of "socket" in all 1000 files with "file", while LogManager stays LogManager no matter what the internal implementation is - I don't have to modify a single file (ecxept for LogManager itself). Your code isn't flexible when it comes to refactoring. And it's not a synthetic example - we've had real cases like that in real projects. >Because you can REDEFINE a function as well. You define the function in one file and it's used in 1000 files. You redefine it in one place it's redefined in everywhere. If you pass dependencies via function arguments, your new implementation of the function will most likely need a different signature (as it requires new dependencies) - thus you will also need to patch all call sites in all 1000 files to conform to the new signature. With interfaces, I don't have to do it. >So? I can initialize dependencies and pass it around as well. Same thing as passing a log manager everwhere. In this example, you're basically reinventing OOP with structs, just without the syntax sugar, such as polymorphic functions. If you use that approach properly - dependency injection in constructors with clean method signatures - I agree it's the exact same thing as OOP so I'm not sure what we're arguing about here. >Injecting the dependency into a class necessitates the ENTIRE class to be passed around >Think of LogManager as SocketWrapper. Usually in real code it's not "the entire class", but an interface, which is just a lightweight implementation-agnostic protocol which announces what kind of functionality is available. Your SocketWrapper is indeed an example of passing an entire class around. Readability is also lacking: "Logger" tells exactly what it does: "I can log". If I see SocketWrapper or WrappedSocket, it's not immediately clear what "wrapping a socket" means. And your example passes the logger as a function argument - that's not how properly designed object-oriented code works - you inject your dependency in the constructor of your class as part of the dependency tree which is initialized somewhere once. Of course if you pass it as an argument every time there's no difference. >Just putting a socket in a wrapper doesn't change the fact that you have to pass it around all over the place. See my argument above that mentioning "socket" everywhere isn't as flexible when it comes to refactoring. >d1 and d2 are reusable. and modular. I can reuse sockets and files with other things. With a logManager I don't even have access. A socket instance can be injected in the logger's constructor and shared by other classes as well, I'm not sure why you think OOP is less modular in this regard.