Coursify

Mastering Low Level Design (LLD)

Core OOP Principles in Practice

20 mins

Revisit the four pillars of OOP—Abstraction, Encapsulation, Inheritance, and Polymorphism—and learn how to apply them to build modular systems.

Learning Goals

  • Apply Abstraction and Encapsulation to protect system integrity.
  • Evaluate the trade-offs between Inheritance and Composition.
  • Implement Polymorphic behavior to handle varying requirements gracefully.

Core OOP Principles in Practice

Object-Oriented Programming (OOP) is the bedrock of Low Level Design. While many developers are familiar with the definitions of the "Four Pillars," applying them effectively in a real-world system requires a deeper understanding of their practical implications.

The Four Pillars of OOP

1. Abstraction

Abstraction is about focusing on what an object does rather than how it does it. In LLD, we use interfaces and abstract classes to define contracts.

  • Practical Tip: Design your modules so that they depend on interfaces, not concrete implementations. This makes it trivial to swap out logic later.

2. Encapsulation

Encapsulation is the practice of bundling data (attributes) and methods that operate on that data within a single unit (class), and restricting direct access to some of the object's components.

  • Practical Tip: Keep your fields private. Use getters/setters (or properties) only when necessary, and ensure that the object maintains its own internal state integrity.

3. Inheritance

Inheritance allows a class to acquire properties and behaviors from another class. While powerful, it is often overused.

  • Practical Tip: Favor Composition over Inheritance. Inheritance creates a "is-a" relationship, which is often too rigid. Use it only when there is a true hierarchical relationship.

4. Polymorphism

Polymorphism allows objects of different types to be treated as objects of a common base type.

  • Practical Tip: Leverage method overriding and interfaces to allow your system to handle new types of objects without changing the core processing logic.

Refactoring: From Procedural to Object-Oriented

  1. 1
    Step 1

    Suppose we have a script that calculates areas for different shapes. In a procedural approach, you might have a single function with a giant switch statement that checks a type string and performs math.

  2. 2
    Step 2

    Define a common interface or abstract class. In this case, an IShape interface with a calculateArea() method. This establishes the contract that all shapes must fulfill.

  3. 3
    Step 3

    Create specific classes like Circle, Rectangle, and Square. Each class encapsulates its own data (e.g., radius for Circle) and provides its own implementation of calculateArea().

  4. 4
    Step 4

    Instead of a switch statement, your main logic now takes a list of IShape objects and calls calculateArea() on each. The system doesn't need to know the specific type; it just trusts the interface.

Example: A Payment Processing System

Let's look at how these pillars come together in a Payment System design.

In this design:

  • Abstraction: IPaymentProcessor hides the complexity of different vendors.
  • Encapsulation: CreditCardProcessor keeps its apiKey private.
  • Polymorphism: PaymentService can work with any processor that implements the interface.
  • Composition: PaymentService has-a processor, it doesn't inherit from one.

Knowledge Check

Question 1 of 3
Q1Single choice

Which OOP pillar is most directly violated when a class allows external code to directly modify its private internal state?