Four Pillars of Object-Oriented Programming
Object-oriented programming pillars for modelling behaviour, state, and reuse.
The four classic pillars of object-oriented programming are abstraction, encapsulation, inheritance, and polymorphism. They are often taught as vocabulary, but their value only becomes clear when you treat them as design tools for managing change.
Abstraction
Abstraction means exposing the behaviour a caller needs while hiding the implementation details that should stay flexible. A payment processor object might offer authorise() and capture() without revealing which HTTP endpoints, retry rules, or credential flows sit underneath.
Good abstraction reduces cognitive load. Poor abstraction merely hides important facts. If callers still need to know internal quirks to use an object safely, the abstraction is weak.
Encapsulation
Encapsulation means keeping data and the operations that protect its invariants together. Instead of allowing arbitrary writes to an object’s internal state, the object exposes controlled methods that preserve consistency.
For example, an Account object should not let any caller set balance directly. It should expose operations such as deposit() or withdraw() that enforce business rules. The benefit is not privacy for its own sake. It is preventing invalid state.
Inheritance
Inheritance allows one class to derive behaviour and structure from another. It can reduce duplication when types genuinely share a stable “is-a” relationship.
The danger is that inheritance also couples subclasses tightly to parent implementation and evolution. If the base class changes in ways children did not expect, the hierarchy becomes fragile. That is why many modern codebases prefer composition for behaviour reuse unless the inheritance model is truly natural.
Polymorphism
Polymorphism means different types can be used through a shared interface while each type provides its own implementation. A notification service can call send() on email, SMS, and push implementations without knowing the transport details.
This is powerful because it lets new behaviour be added without rewriting every caller. The shared contract matters more than the class hierarchy itself.
How the pillars work together
Encapsulation protects invariants. Abstraction makes interfaces easier to use. Polymorphism lets callers depend on behaviour rather than concrete types. Inheritance can provide reuse, but should be applied carefully.
The common mistake is to treat all four as goals in equal measure. They are not. Overusing inheritance or building elaborate abstractions can make code worse, not better.
Practical takeaway
The real test of object-oriented design is not whether a class diagram looks clever. It is whether the codebase becomes easier to extend without breaking existing behaviour. Used well, these pillars help you localise change and express domain rules clearly. Used mechanically, they create unnecessary layers and rigid hierarchies.
That is why understanding the tradeoffs matters more than memorising the names.