Object-Oriented Design

Encapsulation

Hide an object's state behind a small interface so it can protect its own invariants.

Level: beginner

What is it?

Encapsulation bundles data with the methods that operate on it and restricts direct access to that data. Fields are private or protected, and callers go through public methods that enforce rules. The goal is to protect invariants — facts that must always be true, like 'balance is never negative' — by giving them one place to be checked.

Why does it matter?

If any code can change any field, every line in the program is responsible for the invariant, and a bug can be anywhere. When only the class can change its state, the search for a bug starts and ends there, and you can change internals without breaking callers.

Mental model

A bank teller window. You do not walk into the vault and take cash; you slide a request through the window, and the teller checks it, updates the ledger, and hands back money. The window is the public interface. The vault is private state.

How it works

  • Make fields private and expose behavior, not raw data: withdraw(amount) rather than setBalance(x).
  • An invariant is a rule about the object's state that every public method must preserve.
  • Validate in the constructor and in mutators so an object can never be observed in an invalid state.
  • Getters and setters on every field are not encapsulation. They expose the representation under a different name.
  • Returning mutable internals (a list) lets callers change state behind your back; return a copy or a read-only view.
  • Access modifiers: private, protected, public (Java, C++, C#); underscore convention in Python; # fields in JavaScript.

Steps

  • Hide the fields
  • Expose a small set of methods
  • Check the rules on each call
  • Keep the invariant true
  • Change internals freely later

Code example: The class guards its own invariant

public class Account {
    private long cents;                    // hidden; invariant: cents >= 0

    public void deposit(long amount) {
        if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
        cents += amount;
    }

    public void withdraw(long amount) {
        if (amount <= 0 || amount > cents) throw new IllegalArgumentException("invalid amount");
        cents -= amount;
    }

    public long balance() { return cents; }
}

Real-world usage

  • Any type with rules about its state: money, ranges, schedules, state machines
  • Library APIs that need to change internals without breaking users
  • Thread-safe types that protect their state with a lock

Common mistakes

  • Exposing a getter that returns the internal mutable list.
  • Generating a getter and setter for every field and calling it encapsulation.
  • Letting constructors create objects that break the invariant.

Interview questions

What is encapsulation and why does it matter?

Bundling state with the methods that act on it and hiding the state behind a small interface. It lets the class enforce its invariants and lets you change the implementation without breaking callers.

Encapsulation versus abstraction?

Encapsulation hides how state is stored and changed. Abstraction hides complexity by exposing only what callers need to know. Encapsulation is a technique that supports abstraction.

Why can returning a list from a getter break encapsulation?

The caller receives a reference to the internal list and can modify it without going through your methods. Return a copy, an unmodifiable view, or an immutable type.

Prerequisites

Related concepts

Continue learning

Browse the full computer science library