The practical OOP

Reading time ~4 minutes

Polymorphism, Inheritance, Encapsulation are mysterious words.

OOP is many years away from its buzzword status. Everyone knows the keywords, everyone applies the mechanisms, but much fewer people (including me) actually speak them. Why?

The names of these tree cornerstone OOP concepts seem overdressed to me. They are fancy and trigger possibly unrelated imaginations. Their explanation is frequently rooted in theories and solution modeling. This is all truly beautiful. However, what’s the bare minimum to describe the concepts addressing practical issues?

Spending fewer lines

If Polymorphism, Inheritance, Encapsulation are indeed indispensable, what is their ultimate economic benefit?

To beat the competition in the software business, you need to address two problems:

  • Respond fast to changes in required functionality.
  • Avoid the human writing speed bottleneck1.

A simple try is to write less2 (compared to a language without OOP support).

Edsger Dijkstra once said:

My point today is that, if we wish to count lines of code, we should not regard them as “lines produced” but as “lines spent”: the current conventional wisdom is so foolish as to book that count on the wrong side of the ledger.

Surface of potential changes


To highlight the purpose, word “polymorphism” should not bring any associations with (poly)morphing, any morphing.

What polymorphism does in its core is something different - selecting the external code to execute. It delegates, or routes, or dispatches, … operations. It does not morph objects.

We needed support from the language to achieve different behavior (operation) based on different context (object/type). We got polymorphism.

It keeps client code stable no matter which operation gets executed.

class Rectangle extends Shape {
   void draw() { /* draw rectangle */ };
class Sphere extends Shape {
   void draw() { /* draw sphere */ };
void client(Shape s) {
    s.draw(); // select the right code to execute

Again, polymorphism:

  • avoids polymorphing the client code
  • does not polymorphs objects

Sounds contradictory, isn’t it? Well, it is because it is misleadingly defined as the property of the type instead of operation:

A polymorphic type is one whose operations can also be applied to values of some other types.

Hmm… Isn’t it more direct to mean choosing operation based on values? And the term “polymorphic type” does not even make sense in duck typing (e.g. Python) or static polymorphism in C++ (which is also duck typing to me, but a static one) - in these cases, types are not polymorphic.

Any novice in programming will need to google a lot to match it with reality (e.g. to discover differences in static and dynamic type).

So, we are supposed to write less by only adding new operations without changing existing client code. The new operations get selected based on static or dynamic context. Polymorphism (despite the awful term) actually helps.


Inheritance simply sets defaults.

What else can be added to it? Nothing. I’ll just elaborate.

In fact, none of the languages I work with uses inherits keyword - it is almost always extends. And the point is that we just set defaults and override them - quite a mundane phrase instead of mysterious “inheritance”.

We can save many lines of code in our explanation of what Square can do by setting defaults to Rectangle:

class Square extends Rectangle {};

Now, whatever clients could do with Rectangle, they can do with Square. And, by default, it will be the same.

Rectangle r = new Rectangle();

Square s = new Square();

Beyond defaults, inheritance is often used to enforce type safety or setting other constraints (like Square is Rectangle). I count that as a side effect because there can always be different syntactic support for just setting these constraints.

So, we definitely write less if defaults are reused. Inheritance (despite the pompous term) helps too.



Encapsulation is prevention from relying on unstable (internal) details. That’s it.

How does it help us to write less?

  • It may not help us to add less code.
  • But it helps us to change less code.

When unstable details change, there is no code which relies on it (and need to be modified in response).

Encapsulation is probably the only word that suggests its own meaning. We just define the component’s boundary: external and internal parts, or public interface and private implementation.

The approach enforces one of the fundamental design principles - minimize the impact of (typical) future changes. Violations can be caught without writing tests right at compile time - access to private members from the outside of the class will fail… (I’m sorry for Python here).

So, we proactively stop clients from changes (prohibiting them from using “uninsured” operations). Should we rename encapsulation to “insurance”? Nah, that drives even more unrelated imaginations.


  • Polymorphism: reuses client code (for new operations).
  • Inheritance: reuses defaults.
  • Encapsulation: keeps client code stable.

If use of Polymorphism, Inheritance, Encapsulation does not make you change less, you are probably using it wrong.

  1. The primitive way to look at the code writing limit is measuring typing speed. However, if typing was a problem, there would already be some solutions to produce rapid output. The real issue is accepting code in production - code should be thought out to meet requirements, fit well in architecture, written readable for future changes, debugged, reviewed, tested in integration, etc. Choosing one programming language over another may help to reduce the amount code per given functionality - this moves you away from reaching the limits. And the question is how applying OOP mechanisms serve this purpose. 

  2. It is important to point out that small code size alone without readability is, of course, useless. You need a read-write code, not a write-only-and-forget garbage. But we are not comparing languages or styles here. Instead, we compare the same choice of language/style with and without applying OOP techniques.