Labs ICT
⭐ Pro Login

Abstraction vs Encapsulation

Distinguishing between abstraction and encapsulation β€” two pillars of OO.

Abstraction β€” Hiding Complexity

Abstraction is the process of hiding complex implementation details and showing only the essential features of an object. It answers the question: "What does this object do?" without answering "How does it do it?"

Think about driving a car. You don't need to understand how the fuel injection system works, how the transmission shifts gears, or how the electrical system manages the spark plugs. You just need to know: turn the key, press the gas, turn the steering wheel. That's abstraction in action.

In programming, abstraction is achieved through abstract classes and interfaces. They define what operations are available but leave the how to concrete implementations.

// Abstraction β€” defines WHAT, not HOW
class Shape {
  area() {
    throw new Error("area() must be implemented by subclass");
  }

  perimeter() {
    throw new Error("perimeter() must be implemented by subclass");
  }

  describe() {
    return `Area: ${this.area()}, Perimeter: ${this.perimeter()}`;
  }
}

// Concrete implementations provide the HOW
class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }

  perimeter() {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }

  perimeter() {
    return 2 * (this.width + this.height);
  }
}

// Client code only knows about Shape β€” not the details
function printShapeInfo(shape) {
  console.log(shape.describe()); // Works for ANY shape
}

printShapeInfo(new Circle(5));       // Area: 78.54, Perimeter: 31.42
printShapeInfo(new Rectangle(4, 6)); // Area: 24, Perimeter: 20

Encapsulation β€” Hiding Internal State

Encapsulation is the bundling of data with the methods that operate on that data, while restricting direct access to the object's internal state. It answers the question: "How do I protect this object's data from being misused?"

Encapsulation uses access modifiers (public, private, protected) to control what's visible to the outside world. The internal representation is hidden, and interactions happen through a well-defined public interface.

class Account {
  // Private state β€” hidden from outside
  #balance;
  #transactionLog = [];

  constructor(owner, initialBalance) {
    this.#balance = initialBalance;
    this.owner = owner;
    this.#logTransaction("ACCOUNT_OPENED", initialBalance);
  }

  // Public interface β€” controlled access
  deposit(amount) {
    this.#validateAmount(amount);
    this.#balance += amount;
    this.#logTransaction("DEPOSIT", amount);
    return this.#balance;
  }

  withdraw(amount) {
    this.#validateAmount(amount);
    if (amount > this.#balance) {
      throw new Error("Insufficient funds");
    }
    this.#balance -= amount;
    this.#logTransaction("WITHDRAWAL", -amount);
    return this.#balance;
  }

  get balance() {
    return this.#balance;
  }

  getStatement() {
    return [...this.#transactionLog];
  }

  // Private methods β€” internal implementation
  #validateAmount(amount) {
    if (typeof amount !== "number" || amount <= 0) {
      throw new Error("Invalid amount");
    }
  }

  #logTransaction(type, amount) {
    this.#transactionLog.push({
      type,
      amount,
      date: new Date(),
      balance: this.#balance,
    });
  }
}

const acc = new Account("Bob", 1000);
acc.deposit(500);
acc.withdraw(200);
console.log(acc.balance);      // 1300
console.log(acc.getStatement()); // full transaction log
// acc.#balance = 99999;          ← Error! Can't access private state

Abstraction vs Encapsulation β€” The Key Difference

These two concepts are often confused, but they serve different purposes:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Abstraction   β”‚       Encapsulation             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Hides           β”‚ Hides                           β”‚
β”‚ COMPLEXITY      β”‚ IMPLEMENTATION                  β”‚
β”‚                 β”‚                                 β”‚
β”‚ Focuses on      β”‚ Focuses on                      β”‚
β”‚ WHAT an object  β”‚ HOW an object                   β”‚
β”‚ does            β”‚ stores its data                 β”‚
β”‚                 β”‚                                 β”‚
β”‚ Achieved via    β”‚ Achieved via                    β”‚
β”‚ interfaces,     β”‚ private fields,                 β”‚
β”‚ abstract classesβ”‚ access modifiers                β”‚
β”‚                 β”‚                                 β”‚
β”‚ Reduces         β”‚ Protects                        β”‚
β”‚ complexity      β”‚ data integrity                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

A simple analogy: a car's dashboard is abstraction β€” it shows you speed, fuel level, and temperature without exposing the engine's internals. The car's sealed engine compartment is encapsulation β€” it prevents you from randomly touching engine parts and potentially breaking things.

Working Together

In practice, abstraction and encapsulation work hand in hand. Abstraction defines the interface (what operations are available), and encapsulation protects the internal state (how those operations are implemented).

// Abstraction: PaymentProcessor defines what you can do
class PaymentProcessor {
  processPayment(amount) { throw new Error("Implement me"); }
  refund(transactionId) { throw new Error("Implement me"); }
  getTransactionStatus(id) { throw new Error("Implement me"); }
}

// Encapsulation: concrete classes hide HOW they work
class CreditCardProcessor extends PaymentProcessor {
  #apiKey;
  #transactions = new Map();

  constructor(apiKey) {
    super();
    this.#apiKey = apiKey; // hidden
  }

  processPayment(amount) {
    // Complex credit card processing logic is hidden
    const transactionId = this.#generateId();
    this.#transactions.set(transactionId, { amount, status: "completed" });
    return transactionId;
  }

  #generateId() {
    return "txn_" + Math.random().toString(36).substr(2, 9);
  }

  // ... other methods
}

πŸ§ͺ Quick Quiz

What is the main purpose of encapsulation?