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.