A tangent here, but a place where object-orientation works better than anything else is in simulations of the real world. Thus, nothing to do with databases, and in fact it is referred to as object oriented modelling (OOM) rather than OOP.
The way it works is that, let's say you are modelling a pump. The pump is made up of a motor, a shell, the fluid with its own conservation equations etc. In such cases, inheritance and aggregation are godsends that allow us to quickly compose real-life objects as aggregates of easily modified and understood sub-components. Also, once you've taken these sub components to a point where you trust them, there is no further need to spend any time thinking about them. They are there, and you trust them, and you can reuse them and combine them with confidence.
More than anything, it gives us a great way to think about the modelling problem. The success of Modelica speaks to the superiority of this paradigm over things that came before it.
Eh. Most processes in the physical world are reciprocal and most relationships bidirectional. OOP wants you to add a method to one class. So you're either figuring out which billiard ball gets the 'collide' method call or you end up modelling very abstract concepts like CollisionPolicy and the "real world modelling" starts looking more and more like functional abstractions OnlyWithMoreNouns.
If you are giving your ball a collide method, or creating a CollisionPolicy, then you are probably doing it wrong. Therein lies a huge part of the problem of OO design: modelling is difficult, and lots of us get it wrong.
Just because you use FP or some other paradigm does not automatically mean you will get it right. I have seen poorly modelled solutions written in many different languages. I have also seen elegant solutions implemented in a variety of forms as well.
I think that having some depth of experience in a language, as well as a solid grasp of the problem, and perhaps some anal retentive tendencies (i.e. an innate desire to keep things very structured and consistent), can go a long way to solving problems elegantly :-)
Yeah, as with everything, I guess languages need balance. In the case of Modelica (and others inspired by it), we can have functions that are not part of any class at all. So, you could just have a function called evalCollision that accepts two instances of the class BilliardBall and calculate the net result.
My main point, though, was that these new languages are way better than Simulink etc. that went before them.
What you are describing is actually much easier to model with abstractions like multimethods (lisp) or type classes (Scala or Haskell). Traditional OOP is fundamentally hierarchical, but the world isn't. This produces the soup of AbstractFactories and design patterns and also is a cause of the expression problem.
Multimethods allow dispatch on more than one argument (versus one ('this') for traditional OOP). Typeclasses decouple behavior from type hierarchies like Java interfaces do, except that they are extensible.
OOP is orthogonal to FP. That said, I'm increasingly of the opinion that it brings little to the table when a language already has typeclasses. I primarily use Scala, which supports typeclasses through traits and implicits. Just about every time I've modelled something in a traditional OOP way with abstract classes and such, it winds up becoming brittle and I refactor the behavior into typeclasses. I got burned by this enough times that I literally never use OOP hierarchies in my code, except to model algebraic data types.
Note that typeclasses and multimethods aren't fundamentally specific to FP, but they are mainly found in FP languages.
I found that trying to solve the Advent of Code (https://adventofcode.com/) - once they were describing a problem where people were doing things, I'd actually model out the people and the stuff they would do. Elves fighting? Awesome, let's abstract that out. They all get stats and attack functions and make decisions. Elves in an assembly line? Cool, make each one take care of their own work.
So much easier conceptually than trying to use the math to abstract it all down.
Yep, did spacecraft simulation work years ago using Ada95, which was nice in that Ada's OOP support was largely orthogonal to the rest of the language (you could use OOP only where it made sense), unlike Java...
I think this is key. I believe OOP came out of modeling simulations, and it seems well suited for that. It starts to go wrong when you use it as a general purpose paradigm.
The way it works is that, let's say you are modelling a pump. The pump is made up of a motor, a shell, the fluid with its own conservation equations etc. In such cases, inheritance and aggregation are godsends that allow us to quickly compose real-life objects as aggregates of easily modified and understood sub-components. Also, once you've taken these sub components to a point where you trust them, there is no further need to spend any time thinking about them. They are there, and you trust them, and you can reuse them and combine them with confidence.
More than anything, it gives us a great way to think about the modelling problem. The success of Modelica speaks to the superiority of this paradigm over things that came before it.