I have a problem with OOP. There, I’ve said it. It’s probably some residual anger stemming from university days where it’s been taught as the de-facto truth and God to worship (via Java). I also dislike Java, though in the case of Java it’s exactly this reason (and a bit due to the ocean of security advisories around JVM).
But for OOP, there has always been something else as well. It’s about objects themselves. As it’s been sold to me initially, OOP should help us conceptualise applications in term of objects representing real-world models which we can then manipulate. These objects expose only what’s needed to work with them (abstraction), they can relate to one another (inheritance) and can adjust their behaviour (polymorphism). Above all though, objects safeguard their data (encapsulation).
What makes an object, it’s an instance of a class that has some data and has methods that work with that data (eg: has access to the current instance, aka its state).
So what’s the problem?
Well, the most basic problem (and one that’s been widely ‘solved’ by new best practices in OOP development) is the insane entanglement of classes and their relationships that results in an unmaintainable untestable mess. Nowadays OOP masters advocate composition over inheritance so there goes one pillar of OOP.
Next, it’s the conceptual idea that objects have agency. They have methods that manipulate data (and perhaps other objects). One issue that once a method works with “this” and another object instance, that object instance is technically part of the object state itself. The second is the idea that objects can do stuff, which contradicts the purpose of modelling applications on real world concepts. A chair has dimensions, position, etc but doesn’t do stuff (it doesn’t move itself, it doesn’t change its length on its own). We do stuff to the chair, either by creating improved copies or changing a particular chair. This model doesn’t fit the real world. In the real world actions are separate from the data. The data is inert in its state and then gets altered. Encapsulation doesn’t work when applied to the letter. To fix the mess, OOP adopted concepts like immutability (though objects were designed to change state) and added the concept of static methods (really, why? static methods don’t act on the object instance, but they are simply attached to a class they don’t really belong to … instead of being simple functions in a given namespace). The second pillar of OOP is broken, though barely standing.
Polymorphism — that’s not a concept unique to OOP though. Functional programming does it better (arguably). Since all functions are idempotent and immutability is a core concept, function currying provides a much more flexible and reliable way. It’s a thin pillar.
Abstraction — it’s one thing I’ve appreciated but after seeing the light of functional programming, I can’t really see it as a redeeming feature. It’s necessary due to all the unnecessary complexity to a degree that designing things OOP way feels like preemptive optimisation and over-engineering that intuitively I can say it shouldn’t be needed in the first place.
I’ve seen this little gem — I had to take a break seeing how anti-OOP this guy is, but there are plenty of valid points there. He even has a few examples where procedural (well, I still prefer functional) code just looks cleaner than OOP.
And in case you’re wondering why neither is the norm, this could be why.
Under scrutiny, OOP has lost two pillars and the remaining two barely stand. If you do believe the last video above and consider that the high computing cost of immutability has made functional programming a third-class citizen, OOP tries to adopt the same immutability in contexts where compute resources are already stretched (Java) or there are various degrees of inefficiency (PHP, for example) when it’s not like the vast entanglement of classes does any favours in the first place.
So there are some open questions: if we take inheritance out of the equation, why don’t we simply turn static methods into plain functions belonging in a namespace? If we like immutability, why call methods on classes at all instead of passing instances to plain functions that live in a relevant namespace?
To provide a shift given the above chair example, image yourself in the OOP way: you have a chair and you call a “move” method. You’re telling that chair to move according to the method definition which technically belongs to chairs (perhaps, unless it’s been altered by different object types that may or may not inherit and/or alter the method). You don’t know what the “move” actually does until you’ve examined the output.
In the functional way, I “create” a move function on the fly which I then apply. Given a chair, I apply a (curried) function “move” that may be tailored to that particular chair (eg: perhaps a shorted displacement if the chair is too heavy). Given a chair (any chair), I apply a function and receive an outcome. It’s either a generic move function or a function that I’ve created on the fly seeing the chair, so it’s totally known and under control. As far as the real world is concerned, this sounds like a more natural approach.
It’s a tiny but important shift in mentality.