Comment by rdevilla
20 hours ago
I agree. Mostly they are copes for lack of first-class functions and multiple dispatch. Go through GoF and you will see this is the case for 80% of the patterns.
OOP has no firm theoretical foundation, unlike FP which is rooted in the formalisms of mathematics.
Ok, I'm in an argumentative mood, and I think this is more true than not.
The first theoretical foundation of OOP is structural induction. If you design a class such that (1) the constructor enforces an invariant and (2) every public method maintains that invariant, then by induction it holds all the time. The access modifiers on methods help formalise and enforce that. You can do something similar in a functional language, or even in C if you're disciplined (especially with pointers), but it was an explicit design goal of the C++/Java/C# strand of OOP to anchor that in the language.
The second theoretical foundation is subtyping or Liskov substitution, a bit of simple category theory - which gets you things like contravariance on return types and various calculi depending on how your generics work. Unfortunately the C++ people decided to implement the idea with subclassing which turned out to be a mess, whereas interface subtyping gets you what you probably wanted in the first place, and still gives you formalisms like Array[T] <= Iterable[S] for any S >= T (or even X[T] <= Y[S] for S >= T and X[_] <= Y[_] if you define subtyping on functors). In Java nowadays you have a Consumer<T> that acts as a (side-effectful) function (T => void) but composes with a Consumer<? super T> to get the type system right [1].
Whether most Java/OOP programmers realise the second point is another question.
[1] https://docs.oracle.com/en/java/javase/21/docs/api/java.base...