>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. 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.
The only problem with my code and I mentioned it before is that annoying extra variable. To solve your refactoring problem you write an interface to a single class then implement that class how you like.
To solve my problem you write an Interface to both the Function and the dependency. That's it. Same problem as I stated above with the annoying extra variable.
Let me illustrate:
FunctionType: (d: DependencyInterface, s: str) -> void //This is defining a type alias similar to interfaces for classes but this is for functions.
logFile(d: FileImplimentation, s: str): FunctionType = ...
logSocket(d: SocketImplementation, s: str): FunctionType = ...
generalLogFunction(d, s): FunctionType = <logFile or logSocket>
doStuff(d: DependencyInterface){
...
generalLogFunction(d, "hello world")
...
}
Now if you want to change generalLogFunction just create a new implementation to the function type signature and the dependency interface. Same thing has to be done for the class version of the interface
My example is ALSO real world. Many variables that are externally managed are passed around and function signatures take type these parameters as interface parameters. Golang is an popular example with their CTX that they pass all over the place.
>In this example, you're basically reinventing OOP with structs, just without the syntax sugar, such as polymorphic functions.
No. The delta between OOP and other forms of programming is that OOP scopes methods and data together. There isn't an instance where I do that. Sure you can call it syntactic sugar in the sense that python is syntactic sugar for assembly language, It's all turing complete anyway.
>Usually in real code it's not "the entire class", but an interface.
I am aware of what an interface is. In real code an interface doesn't actually exist. It's just type checking guidelines. The actual data moving around in real code is instances of instances of these interfaces.
>Your SocketWrapper is indeed an example of passing an entire class around.
So? You want me to generalize it? It's an example. Generalizing it doesn't prove my point.
>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.
It's just an example. LoggerSocket is equivalent to WrappedSocket. That's the point I'm getting across, they are the same concept. But it Looks like you wanted to talk in terms of interfaces. Well I explained how that works in this comment.
> And your example passes the logger as a function argument - that's not how properly designed object-oriented code works -
There is no properly designed OOP code. Everyone has opinions, you come from the enterprise sector where you guys take certain design patterns from people like Martin Fowler as axiomatic. No man... that's just one flavor of programming, EVEN from the OOP perspective. There are other flavors... see Alan Kay.
>you inject your dependency in the constructor of your class as part of the dependency tree which is initialized somewhere once.
Doesn't this change the signature of the class and therefore break the interface? Well if you make the constructor of the class accept an Interface for a dependency, well then problem solved. But isn't that exactly how I solved it with my function example above? Boom.
>See my argument above that mentioning "socket" everywhere isn't as flexible when it comes to refactoring.
I already countered this point above. For functions you can solve this problem by using an interface for the dependency. If the dependency must be injected anyway then it must be in itself an interface to the interface. If you don't do dependency injection well, this is the only advantage OOP has which I conceded to in my first post: No annoying extra variable. But the Refactoring problem is effectively identical.
>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.
Again, Doesn't that break the interface?
A logger is a bad example for where OOP breaks down actually because all log functions are IO based and return void so they can't be composed anyway. A better example is this:
class IntegerAdditionOperations:
int i = ..
void add(x: int){i += x}
void minus(x: int){i -= x}
...
class IntegerMultOperations: ...
int i;
void mult(x: int)
How would I create a class using ONLY the primitives above to add 5 minus 6 and multiple by 10 with a member variable? Kind of hard to compose these things.
With stateless functions it's trivial.
add(x, y)
sub(x, y)
mult(x, y)
add5minus6andMult10(x) = mult(sub(add(x, 5), 6), 10)