Why you’ve probably got Object-Oriented Programming wrong all this time 🤯

Most people were taught OOP means organising code into classes, using inheritance to share behaviour, and exposing/manipulating state via getters and setters. This has led to bloated, brittle code and side-effect-ridden systems that are hard to change.

But that was never the intention!

Alan Kay, who coined the term in the late ’60s, had something very different in mind. He saw OOP as a way to build systems from independent, self-contained objects – like small computers – that communicate by sending messages.

So where did it all go wrong?

Languages like C++ and Java formalised classes and inheritance as core features. Academia followed, teaching the “4 pillars” of OOP – encapsulation, abstraction, inheritance, and polymorphism – often illustrated with real-world analogies like cats inheriting from animals or shapes extending other shapes 🤦‍♀️

This encouraged developers to focus on classification and hierarchy, rather than systems that emphasise behaviour, clear boundaries, and message-based interaction.

Kay later said:

“I’m sorry that I long ago coined the term ‘objects’ for this topic because it gets many people to focus on the lesser idea. The big idea is messaging.”

And:

“OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.”

In other words, OOP was meant to be about communication, modularity, and flexibility – not rigid structures or class hierarchies.

Kay’s original ideas are still just as relevant today. They’re language-agnostic, and they apply just as well in JavaScript, Go, or Rust as they do in Java or C#.

If you’ve got a beef with OOP, aim it at what it became – not what it was meant to be.

What can we do instead?

If you want to align more closely with the original spirit of OOP – and build systems that are easier to understand, change, and scale – here are some heuristics worth considering. These aren’t hard rules. Like any design choice, they come with trade-offs. The key is to think deliberately and apply them where they bring value.

Design small, composable parts that can evolve

Avoid deep inheritance hierarchies. Instead, model systems using small, focused components that you can compose (“composition over inheritance”). This encourages flexibility and separation of concerns.

Let objects own their state and behaviour

Don’t pass state around or expose internals for others to manipulate. Instead, define clear behaviours and interact through messages. This reduces coupling and makes each part easier to reason about in isolation.

Reduce hidden side effects

Use immutable data and pure functions to limit surprises and make behaviour more predictable. This isn’t about functional purity – it’s about making change safer and debugging less painful.

Look to supporting architectural patterns

Approaches like Domain-Driven Design (DDD) and Hexagonal Architecture (aka Ports and Adaptors) both support a more Alan Kay style approach to OOP.

DDD encourages modelling your system around behaviour and intent, not just data structures. Entities and aggregates encapsulate state and logic, while external code interacts through clear, meaningful operations – not by poking at internal data. Bounded contexts also promote modularity and autonomy, aligning closely with the idea of self-contained, message-driven objects.

Hexagonal Architecture reinforces separation of concerns by placing the application’s core logic at the centre and isolating it from external systems like databases, user interfaces, or APIs. Communication happens through defined interfaces (“ports”), with specific technologies plugged in via adapters. This approach makes systems more modular, testable, and adaptable – supporting the kind of clear boundaries and message-based interaction that aligns closely with the original intent of OOP.

Leave a Reply

Your email address will not be published. Required fields are marked *