I should write an article on this someday but I think it's important to emphasize that objects in programming are very much about providing features for programming and shouldn't be thought of as something to model real world phenomena. Whether something inherits from a base class should depend on whether you're trying to reuse or extend previously written code and it makes sense to do so. But even that's an oversimplification because the decision on whether to reuse code and how difficult in general.
Part of the issue is that inheritance functions like a "module system", and most object oriented languages have module systems that aren't object oriented (C++ has header files, in addition to OOP inheritance) and the differences between these two systems add up to complicated tradeoffs. Sometimes it's obvious which one to use and sometimes it isn't.
This is a very good point. Objects work fine when they're just datastructures like lists/hashmaps or GUI stuff. In those cases the right interface is mostly obvious and inheritance works fine because the focus is on explicit code reuse rather than conceptual modelling.
The modelling/design is what always leads to trouble. UML set us back 50 years. Sadly OOP and OOD almost always come together because that is how they are taught.
I still prefer to work with public data and an open set of functions, but that's a preference, not a stance. If objects are the best way to deal with a problem, I'll use them.
So the actual essay I mean to write is that OOP is really a collection of concepts, which are dynamic memory allocation, a (runtime?) type system, a module system, and a dynamic dispatch system (and I might be missing some). In many languages these concepts either already exist and can be used to put together an object system (like how JavaScript, python, or lua have non native classes), or don't and need an object system to provide them. Oftentimes you just need some of these features, and having to make an object every time is what makes the whole paradigm annoying.
So if you just need dynamic dispatch for example you're stuck putting your functions into a class. If you just wanna reuse some function on different variables you now have to allocate the whole base class. If you want to call a function common to all objects in a collection you have to make sure they all have the same base class so they can be treated as the same type. These are examples off the top of my head so they may not be fully accurate but you get the point.
Square and rectangles, even the taxonomy example, while compelling in your writing, could be defended as great use cases for OOP — the article merely expose naïve implementation, not misapplication
Let's illustrate with moderate depth the taxonomy of life, a great use case for OOP.
Hierarchy
Life
├── Plantae
└── Animal
└── Mammal
├── Dog
├── Cat
└── Dolphin
- Core traits (e.g., warm-bloodedness) propagate upward via inheritance
- Shared behaviors (e.g mammal.eat()) are defined at highest applicable level
- Behaviors can be override
These are basically what inheritance gives us. But inheritance != OOP (that's more than that)
So how to deal with Cross-Cutting Concerns (e.g Carnivore, Aquatic, younameit)?
**Composition**
-> Attach traits like Carnivore or Aquatic as components (or properties, if you like, properties can be classes.
Or
**Interfaces**
-> I favour composing, but interfaces can be implemented by classes. Or the interfaces provides the implementation.
See the Serializable interface in Java, for a beautiful abdtraction that saved the need for millions of developers to have a single clue how that works, yet their stated does serialize just fine.
Back to our taxonomy of life:
A Diet component could drive hunt() logic, with context (e.g., "walk" vs. "dive") determined by other traits.
Interfaces: Use ICarnivore or IAquatic to enforce capabilities without inheritance. A dolphin implements both, inheriting swim() from IAquatic and overriding hunt() via Diet.
Flexibility Without Chaos
Common DNA/gene logic lives in Life, inherited by all.
Niche behaviors (e.g., hunting style) are encapsulated in composable modules, avoiding brittle hierarchies.
I would suggested exploring how OOP can address all the concerns your article discussed. It's not to say OOP is a magic bullet everyone should force into everything, I agree with many times it is forced in, induces complexity, bugs for no benefits. But the examples you used could well be turned around with "design patterns", but also good judgement.
Thanks for the comment but I must say I don't find your counter-examples compelling.
The taxonomy of life is, IMO, a perfect example of doing it *wrong*. If I'm developing a game, for example, the last thing I want is for a dolphin to share behavior with other mammals. If anything it'll behave mostly like a fish. At best they might all inherit from some GameObject class.
Composition has nothing to do with OOP, you can do composition in all paradigms.
I'm a fan of interfaces (I mentioned it in passing in the post) but your first example, Serializable, is the worst you could have picked! That interface has no methods, it's a giant hack that gets special treatment from the Java compiler. There's a list of optional methods that can be implemented but they're only written out in the docs! The power of serializable comes from reflection, which has nothing to do with OOP (in fact, anti-OOP since you're poking at private fields).
So ICarnivore/IAquatic? Good, but those can also be traits like in Rust. OOP-ish but not really (traits come from haskell typeclasses, a functional language). In the end, what matters is what you're trying to achieve, and OOP may or may not help with that, but people apply it regardless, which is the problem.
encapsulation, is are core principles of OOP, I concede it doesn't have exclusivity.
To be clear I'm not defending OOP or advocating for its systematic use. I would even add it isn't for everyone.
I agree overall, with the core argument you are making. OOP can and often does make things worse. Yielding no benefits, only constraints and non sensible functions. Did I concur?
Imo functional can make things worse too, no need for hierarchies to confuse and create a monster of inheritance with classes having no head nor tails. Coders struggle, knowing a good set, or even arsenal of patterns is necessary to use any of (3?) paradigms. Call it craftsmanship, to make functional look as beautiful as it promises requires that too.
I suffer 20y of OOP development, functional is something I mostly find myself using in side project, open source. Happy to dive deeper, I may very well change my mind and start explaining OOP just doesn't apply here and there.
Thanks for the write ups and taking the time to reply, will read your other articles.
Yeah you got it. Just to be clear I think OOP works well for various problems (I have a private project that implements OOP in C because I need interfaces and encapsulation for the particular problem I'm solving).
My issue is with how it is taught and how it is wrongly applied to the wrong problems just because people think they need to do OOP no matter what. Same is true of functional as you said, some people try to apply it to every problem even when it kills performance and makes it hard to work.
I should write an article on this someday but I think it's important to emphasize that objects in programming are very much about providing features for programming and shouldn't be thought of as something to model real world phenomena. Whether something inherits from a base class should depend on whether you're trying to reuse or extend previously written code and it makes sense to do so. But even that's an oversimplification because the decision on whether to reuse code and how difficult in general.
Part of the issue is that inheritance functions like a "module system", and most object oriented languages have module systems that aren't object oriented (C++ has header files, in addition to OOP inheritance) and the differences between these two systems add up to complicated tradeoffs. Sometimes it's obvious which one to use and sometimes it isn't.
This is a very good point. Objects work fine when they're just datastructures like lists/hashmaps or GUI stuff. In those cases the right interface is mostly obvious and inheritance works fine because the focus is on explicit code reuse rather than conceptual modelling.
The modelling/design is what always leads to trouble. UML set us back 50 years. Sadly OOP and OOD almost always come together because that is how they are taught.
I still prefer to work with public data and an open set of functions, but that's a preference, not a stance. If objects are the best way to deal with a problem, I'll use them.
So the actual essay I mean to write is that OOP is really a collection of concepts, which are dynamic memory allocation, a (runtime?) type system, a module system, and a dynamic dispatch system (and I might be missing some). In many languages these concepts either already exist and can be used to put together an object system (like how JavaScript, python, or lua have non native classes), or don't and need an object system to provide them. Oftentimes you just need some of these features, and having to make an object every time is what makes the whole paradigm annoying.
So if you just need dynamic dispatch for example you're stuck putting your functions into a class. If you just wanna reuse some function on different variables you now have to allocate the whole base class. If you want to call a function common to all objects in a collection you have to make sure they all have the same base class so they can be treated as the same type. These are examples off the top of my head so they may not be fully accurate but you get the point.
Looking forward to the article 😀, agreed on all points.
Square and rectangles, even the taxonomy example, while compelling in your writing, could be defended as great use cases for OOP — the article merely expose naïve implementation, not misapplication
Let's illustrate with moderate depth the taxonomy of life, a great use case for OOP.
Hierarchy
Life
├── Plantae
└── Animal
└── Mammal
├── Dog
├── Cat
└── Dolphin
- Core traits (e.g., warm-bloodedness) propagate upward via inheritance
- Shared behaviors (e.g mammal.eat()) are defined at highest applicable level
- Behaviors can be override
These are basically what inheritance gives us. But inheritance != OOP (that's more than that)
So how to deal with Cross-Cutting Concerns (e.g Carnivore, Aquatic, younameit)?
**Composition**
-> Attach traits like Carnivore or Aquatic as components (or properties, if you like, properties can be classes.
Or
**Interfaces**
-> I favour composing, but interfaces can be implemented by classes. Or the interfaces provides the implementation.
See the Serializable interface in Java, for a beautiful abdtraction that saved the need for millions of developers to have a single clue how that works, yet their stated does serialize just fine.
Back to our taxonomy of life:
A Diet component could drive hunt() logic, with context (e.g., "walk" vs. "dive") determined by other traits.
Interfaces: Use ICarnivore or IAquatic to enforce capabilities without inheritance. A dolphin implements both, inheriting swim() from IAquatic and overriding hunt() via Diet.
Flexibility Without Chaos
Common DNA/gene logic lives in Life, inherited by all.
Niche behaviors (e.g., hunting style) are encapsulated in composable modules, avoiding brittle hierarchies.
I would suggested exploring how OOP can address all the concerns your article discussed. It's not to say OOP is a magic bullet everyone should force into everything, I agree with many times it is forced in, induces complexity, bugs for no benefits. But the examples you used could well be turned around with "design patterns", but also good judgement.
Hi Hirako San,
Thanks for the comment but I must say I don't find your counter-examples compelling.
The taxonomy of life is, IMO, a perfect example of doing it *wrong*. If I'm developing a game, for example, the last thing I want is for a dolphin to share behavior with other mammals. If anything it'll behave mostly like a fish. At best they might all inherit from some GameObject class.
Composition has nothing to do with OOP, you can do composition in all paradigms.
I'm a fan of interfaces (I mentioned it in passing in the post) but your first example, Serializable, is the worst you could have picked! That interface has no methods, it's a giant hack that gets special treatment from the Java compiler. There's a list of optional methods that can be implemented but they're only written out in the docs! The power of serializable comes from reflection, which has nothing to do with OOP (in fact, anti-OOP since you're poking at private fields).
So ICarnivore/IAquatic? Good, but those can also be traits like in Rust. OOP-ish but not really (traits come from haskell typeclasses, a functional language). In the end, what matters is what you're trying to achieve, and OOP may or may not help with that, but people apply it regardless, which is the problem.
encapsulation, is are core principles of OOP, I concede it doesn't have exclusivity.
To be clear I'm not defending OOP or advocating for its systematic use. I would even add it isn't for everyone.
I agree overall, with the core argument you are making. OOP can and often does make things worse. Yielding no benefits, only constraints and non sensible functions. Did I concur?
Imo functional can make things worse too, no need for hierarchies to confuse and create a monster of inheritance with classes having no head nor tails. Coders struggle, knowing a good set, or even arsenal of patterns is necessary to use any of (3?) paradigms. Call it craftsmanship, to make functional look as beautiful as it promises requires that too.
I suffer 20y of OOP development, functional is something I mostly find myself using in side project, open source. Happy to dive deeper, I may very well change my mind and start explaining OOP just doesn't apply here and there.
Thanks for the write ups and taking the time to reply, will read your other articles.
Yeah you got it. Just to be clear I think OOP works well for various problems (I have a private project that implements OOP in C because I need interfaces and encapsulation for the particular problem I'm solving).
My issue is with how it is taught and how it is wrongly applied to the wrong problems just because people think they need to do OOP no matter what. Same is true of functional as you said, some people try to apply it to every problem even when it kills performance and makes it hard to work.