Preface
No matter how many times I tried to write this chapter, it still isn't as good as this video:

I strongly recommend watching it before continuing to read, as it reveals the problems I'm talking about in incredible detail and clarity.
In Previous Chapters
In this chapter, I won't address the problems with the pillars of OOP, as there are enough articles about that (link to one of the best I've found). Instead, I want to touch on a much more important and fundamental problem:
Excessive atomicity of OOP
By "atomicity," I mean the artificial encapsulation that forms the foundation of OOP. And if there's "artificial encapsulation," there must also be "natural encapsulation."
Natural Encapsulation
Natural encapsulation is the hiding of code that happens naturally, due to the design of operating systems, computers, physical laws, etc.
For example, "Closure," "Scope," or "Context" through which we have or don't have access to data from another scope is an example of encapsulation:
const someFn = () => { const someVar = 1; // This variable is only available within this function } const someOtherFn = () => { console.log(someVar) // Thanks to "encapsulation," this variable won't be accessible }
Similarly, natural encapsulation exists at the level of any application: your application's entire codebase is hidden inside it, and it only exposes certain APIs to the outside world.
Another good example of natural encapsulation would be at the level of libraries, frameworks, and SDKs, as we only use the public methods provided by the creators while the internal workings remain untouched.
OOP Encapsulation
OOP suggests dividing our application, which already has natural encapsulation, into even smaller "atoms," which in OOP are called "objects."
The Problem with OOP Encapsulation
Within an application that should be able to operate on available data and processes, we create additional layers of isolation.
It's this excessive isolation that leads to OOP applications losing flexibility.
"Flexibility" is the ability to quickly and conveniently change the logic of an application.
When you divide your codebase into a graph of "objects," you deprive yourself of this flexibility because now any change that doesn't fit into this graph (and there will be 100% such changes) will require either creating workarounds (public properties and ...Service objects) or restructuring the graph with even more confusing connections.
AND YOU CAN'T DO ANYTHING ABOUT IT.
Once you start using OOP and dividing your program into smart "objects," you immediately fall into this trap, and the only way out is to abandon OOP altogether.
If OOP is so bad, why is it used?
There are 2 reasons:
- You can create a quality program using OOP
- Marketing
Creating Programs with OOP
Here's a very interesting thought and the hook that people get caught on when OOP, DDD, Clean Architecture, and similar methodologies are promoted:
You can create a quality program using OOP, and that's true.
Because when you create an application (1) you don't have any existing codebase, (2) you study the problems and tasks from scratch and design an object graph that will perfectly solve all the given problems.
But almost no one talks about what they face when the system is already created and it's time to develop it further.
And virtually any system is first "created" and then "developed."
It's precisely when new requirements appear that you realize all this architecture, which solved problems so well when it was created, doesn't fit the new requirements, and you'll have to "remake the old functionality to add new functionality" rather than simply "adding new functionality."
Someone might say: "That's because you just didn't write the code correctly" – but I would say no, no matter how "correctly" you wrote it, absolutely always, if the system evolves, the initial requirements will become invalid, meaning over time any architecture becomes "incorrect."
AND THERE IS AN APPROACH THAT KEEPS YOUR PROGRAM FLEXIBLE, and that's what this book is dedicated to. I promise you, if you read it to the end and try FOP, you'll be amazed at how truly flexible a program can be and understand why OOP has been taking these possibilities away from you all this time.
Marketing
The second reason for OOP's popularity is marketing.
Java was patient zero for OOP, which made OOP code so popular:
- When Java appeared, there were only procedural and functional programming languages, where the former were too primitive and the latter too complex, so Java began to gain momentum very quickly at that time.
- Java's marketing gained momentum so quickly that many languages decided to take a piece of the audience for themselves and began creating Java-like languages.
What Happened Over Time?
It was the historical context that caused the popularity of OOP code. But time goes on, and we see how people are gradually coming to their senses:
- Languages like Java, Ruby, and C# are adding lambda functions (which is a construct from FP, not OOP)
- Golang, Rust, Kotlin, Zig, and Nim have OOP-like constructs but are multi-paradigm languages, with an emphasis on procedural and even functional programming.
- Simplified functional languages like Elixir, Scala, and F# are emerging
- Unity is moving toward a functional ECS (DOTs) approach instead of the previous OOP approach
Alternatives to OOP
Along with this comes the question: "If not OOP in multi-paradigm languages, then what programming paradigm should we use?"
I asked myself the same question.
In search of an answer, I realized that Functional (FP) and Procedural (PP) programming are not good answers because FP is impossible to write conveniently if your language doesn't support it out of the box, and PP is too outdated and dangerous as a paradigm.
And that's where "Function Oriented Programming" or "FOP" emerged as an alternative to OOP, based on FP but with simplifications from PP.
In the next chapter, we'll briefly go through FP and PP for general knowledge, and then delve into even deeper concepts that marked the creation of FOP:
👈 Previous chapter
Next chapter 👉