Knowledge Base · Concepts

Object-Oriented Programming concepts

Classes, objects, and the four pillars of OOP, encapsulation, abstraction, inheritance, and polymorphism, explained plainly with real code, and honest about when (and when not) to reach for them.

Classes and objects

Object-oriented programming (OOP) organizes code around objects: bundles of data (state) and the functions that operate on it (behavior). A class is the blueprint; an object is a concrete instance of it. A BankAccount class might define a balance and methods like deposit() and withdraw(); each open account is an object with its own balance.

The goal is to keep related state and behavior together, so the rest of your program talks to a small, well-defined surface instead of juggling loose variables. The four ideas below are how OOP delivers that.

The four pillars at a glance

PillarWhat it meansHow you do it
EncapsulationBundle state with behavior; hide the internalsPrivate fields, public methods
AbstractionExpose what it does, hide howInterfaces, abstract methods
InheritanceDerive and extend (an is-a relationship)extends / subclassing
PolymorphismOne interface, many implementationsOverride / implement a method

1. Encapsulation

Encapsulation means bundling data with the methods that use it, and hiding the internals. Callers go through a public interface (deposit()), not the raw fields. The class can enforce its own rules, reject a negative deposit, keep balance consistent, because nothing outside can reach in and corrupt its state. Practically: make fields private, expose intent-revealing methods.

2. Abstraction

Abstraction is exposing what something does while hiding how it does it. You call account.transfer(other, 100) without knowing whether it writes to a database, calls a network service, or updates memory. Good abstractions let you change the implementation without touching the callers. Encapsulation is the mechanism; abstraction is the design goal it serves.

3. Inheritance

Inheritance lets a class derive from another, reusing its state and behavior and extending or overriding parts. A SavingsAccount can inherit from BankAccount and add interest. It models an “is-a” relationship, a savings account is a bank account. Use it sparingly (see inheritance vs composition below).

4. Polymorphism

Polymorphism (“many forms”) means one interface, many implementations. If Circle and Square both implement an area() method, code can loop over a list of Shape and call area() on each without knowing the concrete type. Add a new shape later and the loop does not change. This is what makes OOP code extensible.

All four, in code

The same little example shows every pillar at once. Python:

class BankAccount:
    def __init__(self, balance=0):
        self._balance = balance            # encapsulation: hidden by convention

    def deposit(self, amount):             # the public interface, the only way in
        if amount <= 0:
            raise ValueError("amount must be positive")
        self._balance += amount

    def describe(self):                    # meant to be overridden
        return f"Account: {self._balance}"

class SavingsAccount(BankAccount):         # inheritance: a savings account IS A bank account
    def __init__(self, balance=0, rate=0.02):
        super().__init__(balance)
        self.rate = rate

    def describe(self):                    # polymorphism: same call, different behavior
        return f"Savings: {self._balance} at {self.rate}"

for account in [BankAccount(100), SavingsAccount(500)]:
    print(account.describe())              # one interface, many forms

And the same idea in C# (closer to how the original KB Cafe taught it), with explicit access modifiers:

public abstract class Account {
    private decimal balance;               // encapsulation: the field is private

    public Account(decimal balance) { this.balance = balance; }

    public void Deposit(decimal amount) {  // public method, callers never touch the field
        if (amount <= 0) throw new ArgumentException("positive only");
        balance += amount;
    }

    public abstract string Describe();     // abstraction: what, not how
}

public class Savings : Account {           // inheritance
    public Savings(decimal b) : base(b) { }
    public override string Describe() => "Savings account";  // polymorphism
}

Inheritance vs composition

The most common OOP mistake is over-using inheritance. The modern guidance, “favor composition over inheritance”, means: build behavior by combining small objects (a has-a relationship) rather than inheriting from a tall hierarchy.

Inheritance (is-a)Composition (has-a)
RelationshipA Savings is a AccountA Car has an Engine
CouplingTight, the subclass depends on the parentLoose, swap parts freely
As it growsBrittle, deep trees are fragileStays flexible
Reach for it whenYou have a true, stable subtypeYou want to reuse and combine behavior

Common mistakes

  • Deep inheritance trees. Three-plus levels of subclassing is usually a smell; a change at the top ripples everywhere. Prefer composition.
  • God objects. One class that knows and does everything. Split responsibilities so each class has a single job.
  • Anemic models. Classes that are just bags of public fields with no behavior, that is data, not encapsulation. Put the rules with the data.
  • Getters and setters for everything. Exposing every field through accessors quietly breaks encapsulation; expose intent (deposit()), not raw state.

When OOP helps, and when it doesn’t

  • Good fit: domains with clear entities and rules (orders, users, accounts), large codebases where boundaries matter, and frameworks built around objects.
  • Poor fit: simple data transformations and pipelines, where a functional style (pure functions over plain data) is often clearer. Many modern languages mix both, use the tool that fits the problem rather than forcing everything into a class.

FAQ

What is the difference between a class and an object?

A class is the definition or blueprint; an object is a specific instance created from it, with its own state. One BankAccount class, many account objects, each with its own balance.

What are the four pillars of OOP?

Encapsulation (bundle state with behavior and hide internals), abstraction (expose what, hide how), inheritance (derive and extend an is-a relationship), and polymorphism (one interface, many implementations).

Encapsulation vs abstraction, what is the difference?

They are related but not the same. Encapsulation is the mechanism: make fields private and force access through methods. Abstraction is the goal that mechanism serves: let callers use what an object does without depending on how it works.

Interface vs abstract class, which do I use?

An interface is a pure contract (method signatures, no implementation), and a class can implement many. An abstract class can provide shared implementation but a class extends only one. Reach for an interface to describe a capability, an abstract class to share a partial base.

Why composition over inheritance?

Inheritance couples a subclass tightly to its parent and gets fragile as hierarchies deepen, a change up top can break everything below. Composing small, focused objects (a has-a relationship) is more flexible and easier to change, so prefer it unless you genuinely have a stable is-a subtype.

Are the four pillars language-specific?

No. Encapsulation, abstraction, inheritance, and polymorphism appear across Java, C#, C++, Python, Ruby, and more. Each language expresses them differently (Python uses a leading underscore convention where Java uses the private keyword), but the ideas are the same.

What is a god object, and why is it bad?

A god object is a class that knows or does too much, accumulating unrelated responsibilities until everything depends on it. It defeats encapsulation and makes the code hard to change or test. Split it into focused classes, each with one job.

Do I need OOP for a small script?

Usually not. For a simple data transformation or a short script, plain functions over plain data are clearer. OOP earns its keep when you have real entities with rules and a codebase large enough that boundaries matter. Most modern languages let you mix both.

Related concepts

Design patterns · Smart pointers · Multithreading & concurrency · Memory leaks · C++ type casting · all references.

☕ KB Cafe Classic

“OOP Concepts” was one of kbcafe.com’s most-referenced developer articles in its first life, cited across forums and university course pages through the C# and Java era. This is its modern restoration: the same fundamentals, rewritten with real examples and honest trade-offs for today.