Skip to content

OOP Pillars — Detailed#

1. Encapsulation — state behind a barrier#

Bundle data with the methods that act on it, hide the internals, expose only a contract.

classDiagram
  class BankAccount {
    -balance: BigDecimal
    -ownerId: UUID
    -lock: Lock
    +deposit(amount)
    +withdraw(amount)
    +getBalance() BigDecimal
  }
  note for BankAccount "balance is private.\nNo external code can\nset it directly."
  • Fields stay private. Mutations go through methods that enforce invariants (no negative balance, no overdraft).
  • Without encapsulation, every callsite duplicates business rules — and one will forget.
  • The "barrier" can be a class, a module, a package, or even a process boundary.

2. Abstraction — essential, not incidental#

Expose what a thing does; hide how it does it. Abstractions are the API + the rules; implementations are the code.

classDiagram
  class PaymentGateway {
    <<interface>>
    +charge(amount, card) ChargeResult
    +refund(chargeId) RefundResult
  }
  class StripeGateway
  class AdyenGateway
  class FakeGateway
  PaymentGateway <|.. StripeGateway
  PaymentGateway <|.. AdyenGateway
  PaymentGateway <|.. FakeGateway
  • Callers depend on PaymentGateway, not on Stripe specifics.
  • Swap real ↔ fake in tests; real ↔ different vendor in prod.
  • Abstraction != generalisation. Adding levels "just in case" creates indirection without insight.

3. Inheritance — is-a hierarchy#

Reuse + specialise a base type's behaviour and contract.

classDiagram
  class Vehicle {
    +id
    +seats
    +start()
    +stop()
  }
  class Car {
    +trunkSize
    +openTrunk()
  }
  class Motorcycle {
    +hasSidecar: bool
  }
  class ElectricCar {
    -batteryKwh
    +chargeFor(minutes)
  }
  Vehicle <|-- Car
  Vehicle <|-- Motorcycle
  Car <|-- ElectricCar
  • Subtypes inherit fields + methods, can add their own, can override existing.
  • Misused for code reuse alone — prefer composition instead (see Composition over inheritance).
  • Multiple inheritance: avoided in Java/C#; available via mixins/traits/interfaces in Scala, Rust, Python.

4. Polymorphism — one name, many forms#

Same operation, different behaviour depending on the runtime type.

classDiagram
  class Shape {
    <<abstract>>
    +area() double
  }
  class Circle {
    +r: double
    +area() double
  }
  class Rectangle {
    +w: double
    +h: double
    +area() double
  }
  class Triangle {
    +base: double
    +height: double
    +area() double
  }
  Shape <|-- Circle
  Shape <|-- Rectangle
  Shape <|-- Triangle
double totalArea(List<Shape> shapes) {
  return shapes.stream().mapToDouble(Shape::area).sum();
}

Shape::area dispatches to Circle.area() / Rectangle.area() / etc. The caller doesn't care which.

Flavours#

Flavour Mechanism Example
Subtype / dynamic dispatch virtual methods, vtable Shape::area above
Ad-hoc method overloading print(int) vs print(String)
Parametric generics / templates List<T>, Map<K,V>
Coercion implicit conversion int → long in arithmetic
Duck typing runtime structural Python — "if it quacks like a duck…"

How the pillars compose#

flowchart LR
  subgraph Encap[Encapsulation barriers]
    M[Module / Class]
  end
  subgraph Abs[Abstraction]
    I[Interface]
  end
  subgraph Inh[Inheritance hierarchy]
    A[Abstract] --> B[Concrete]
  end
  subgraph Poly[Polymorphism]
    Call[Caller] -->|same call| Many[Many impls]
  end
  M --> I --> A
  B --> Many

  classDef p fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
  classDef s fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
  class M,I,A,B,Call,Many s;

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class M,I,A,B,Call,Many service;
  • Encapsulation gives you a boundary.
  • Abstraction defines what crosses that boundary.
  • Inheritance shares a contract among types.
  • Polymorphism lets callers work against the contract without knowing the concrete type.

Together: callers depend on abstractions; implementations are encapsulated; new behaviours plug in via inheritance + polymorphism.

Where these show up in this site#

  • Notification systemChannel is an abstraction with concrete PushChannel / SMSChannel / EmailChannel siblings (Abstraction + Polymorphism).
  • Logger frameworkAppender interface, many implementations; loggers compose appenders (Abstraction + Composition).
  • Parking Lot / Vending Machine / ElevatorVehicle, Item, Request hierarchies use Inheritance + Polymorphism.
  • Payment gatewayPaymentMethod interface with Card / UPI / Wallet / BNPL implementations (Abstraction).

Glossary & fundamentals#

Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.

Tag Concept What it is Page
HLD Service mesh sidecar mesh, mTLS, traffic policy service-mesh
LLD Async models futures / async-await / coroutines / actors async-models
LLD OOP pillars encapsulation, abstraction, inheritance, polymorphism oop-pillars
LLD Composition over inheritance prefer composition; small swappable parts composition-over-inheritance