Douglas ’ Blog

OOP and Design Patterns are garbage

~ Feb 26, 2024 ~


When I started my career in programming, I learned that OOP is a tool, and as with every tool, it has its place for solving a problem.

At first, it seemed to make sense, but as I got more experience in programming, I realized that OOP doesn’t provide any benefit. That’s why I wrote to ignore OOP.

The first reason to ignore OOP is that it leads to bad performance:

Another reason is that several books try to patch OOP:

Those books show that OOP is flawed as they provide guidelines to write better OO code. All three books combined have about 1200 pages of heuristics! Try to remember all that!

Michael Feathers wrote a blog post saying that his book “Working Effectively with Legacy Code” should be named “Working Effectively with Object-Oriented Code”.

I wonder why there isn’t a book for working with legacy Functional or Procedural code. Probably because there is no secret technique, it’s just way simple to deal with, so it doesn’t need a book for this.

Another reason is that modern languages, such as Zig, Go, and Elixir, do not explicitly adopt OOP and do not encourage it. Some older languages, like Java, C#, and C++, are moving away from OOP by implementing functional features.

In my first post, I mentioned that Rust does not explicitly adopt OOP, but the official docs show it does. Nonetheless, it seems it is not too much publicized.

Some OOP defenders claim that OOP is best suited to large and complex software. This statement is complete nonsense. There are many large, complex, and successful non-OO projects: Git, Linux, Redis, Nginx, Postgres, and SQLite. You can write large, complex, and successful software in any paradigm, even assembly!

Other defenders claim that OOP is best for GUI code. But this is not the only approach for writing GUI code, like Immediate Mode GUI.

One of the most annoying claims is when someone criticizes an OO code and says that the developer over-engineered the solution and is not OOP’s fault. Developers can over-engineer solutions, but I firmly believe that OOP leads to over-engineered solutions.

To finish this section, I would like to give an example where procedural code is way simpler than OO code. Martin Fowler, who wrote the Refactoring book, rewrote the Video Store example in JavaScript, where he calculates the customer bill and prints the statement.

In the first version, he wrote in Java. The initial code has three classes and 88 lines of code. All this can easily fit in 42 lines of JavaScript. If you add types, it increases to 57, but still less than Java.

You can even translate it to C, which, despite being older than Java, has a certain simplicity that Java lacks.


Since I already discussed that OOP is garbage. Let’s go to Design Patterns. As the title says: “Design Patterns: Elements of Reusable Object-Oriented Software” is specific to OOP.

The only purpose of design patterns is to patch deficiencies in the language, especially C++ and Java. The authors agree that some patterns don’t make sense in another language because they have better mechanisms to solve a problem. The excerpt from the book:

…some of our patterns are supported directly by the less common object-oriented languages. CLOS has multi-methods, for example, which lessen the need for a pattern such as Visitor (page 331). In fact, there are enough differences between Smalltalk and C++ to mean that some patterns can be expressed more easily in one language than the other and most of this languages existed before Java and C++, which are the target languages of the book.

Also, Peter Norvig pointed out that “16 of 23 patterns have a qualitatively simpler implementation in Lisp or Dylan”. Another interesting blog post shows how Clojure simplifies those patterns.

If C++ or Java had been based on Lisp, then the patterns book would never exist.

The choice of programming language is important because it influences one’s point of view. Our patterns assume Smalltalk/C++-level language features, and that choice determines what can and cannot be implemented easily. If we assumed procedural languages, we might have included design patterns called “Inheritance,” “Encapsulation,” and “Polymorphism.”

Inheritance, Encapsulation, and Polymorphism are not patterns! They are simply language features. They do not solve a recurrent problem.

I also believe that design patterns do not describe actual problems but fictional problems from the OOP design mindset.

Some quotes from the book explain a little about OOP design in general.

The hard part about object-oriented design is decomposing a system into objects. The task is difficult because many factors come into play: encapsulation, granularity, dependency, flexibility, performance, evolution, reusability, and on and on. They all influence the decomposition, often in conflicting ways.

Object-oriented design methodologies favor many different approaches. You can write a problem statement, single out the nouns and verbs, and create corresponding classes and operations. Or you can focus on the collaborations and responsibilities in your system. Or you can model the real world and translate the objects found during analysis into design. There will always be disagreement on which approach is best.

This is the fundamental flaw with OOP. It assumes that we should design the code around objects, not the solution. This leads to a weird design and complex structure, similar to this satire project FizzBuzzEnterpriseEdition.

Design patterns solve many of the day-to-day problems object-oriented designers face

They face these problems because OOP is garbage.

An object-oriented program’s run-time structure often bears little resemblance to its code structure. The code structure is frozen at compile-time; it consists of classes in fixed inheritance relationships. A program’s run-time structure consists of rapidly changing networks of communicating objects. In fact, the two structures are largely independent.

With such disparity between a program’s run-time and compile-time structures, it’s clear that code won’t reveal everything about how a system will work. The system’s run-time structure must be imposed more by the designer than the language. The relationships between objects and their types must be designed with great care, because they determine how good or bad the run-time structure is.

This is another reason that OOP leads to bad design. It makes it harder to understand the program by reading it because it is a network of communicating objects.

We don’t consider this collection of design patterns complete and static; it’s more a recording of our current thoughts on design.

So, why there isn’t a second edition after 30 years?

To finish this section, I refactored a simple game. The game made heavy use of the observer pattern. So, as a demonstration, I removed the layers of indirection and simplified the overall code. In the end, the observer pattern was unnecessary.

You can compare yourself, the original code, and my refactored version. Then, see which is easier to read and understand.

Conclusion

OOP became widely used because of the popularity of C++ and Java, not because it is inherently good. OOP does not provide any benefit that people claim. Procedural code is as flexible, modular, and reusable as OO. The idea that OOP is somehow better at this is complete nonsense.

Be careful with anyone ‘selling’ patterns to you. Make sure they serve a purpose and solve your problem. Don’t forget that data structures and algorithms are much more fundamental than any design pattern.

The main takeaway of this post is:

If you ignore OOP, your code will become simpler.

To finish my case against OOP, I selected a list of links that discuss a little about how OOP is bad: