Software design · intermediate

Encapsulation

Encapsulation places a representation and the operations that maintain it behind a deliberately limited interface. Callers depend on supported behavior rather than manipulating internal state in ways that can violate invariants.

Why it matters

A good boundary localizes validation and change: internals can evolve without rewriting callers, and invalid states become harder to construct. Encapsulation can be provided by modules, closures, abstract data types, or objects.

Mental model

How to reason about encapsulation

Define the invariant first, then expose the smallest operations that preserve it. Internal fields and helper structures remain implementation details unless the public contract deliberately includes them.

Analogy

An encapsulated component is a vending machine: customers use a constrained panel of valid operations while inventory, accounting, and dispensing mechanisms stay behind the casing.

Examples

See the boundary, not just the happy path

Worked example · Protect an account invariant

account.withdraw(amount) instead of account.balance -= amount

withdraw can reject non-positive amounts, enforce available funds, record an audit event, and preserve one contract regardless of the balance's internal representation.

Worked example · Return a read-only view

List<Item> get items => List.unmodifiable(_items);

The Dart interface prevents callers from mutating the collection through the returned list. Deeply mutable Item objects may still require their own boundaries.

Useful contrast · Transparent data can be intentional

A coordinate value exposes immutable x and y fields

Not every public field is poor design. When the representation is the stable contract and cannot violate hidden invariants, transparent immutable data can be simpler than ceremonial accessors.

Common mistakes

Misconceptions to remove early

Generating getters and setters for every field

Pass-through accessors preserve the same coupling and may permit the same invalid states. Expose meaningful operations and stable data, not private syntax for its own sake.

Treating encapsulation as a security boundary

Language visibility primarily controls supported program structure. Reflection, unsafe code, serialization, or a hostile process can bypass it; security requires appropriate trust and isolation mechanisms.

Quick check

Can you predict the result?

1. What should primarily determine an encapsulated type's public operations?
  • The behaviors callers need and the invariants those operations must preserve.
  • A getter and setter for every storage field.
  • The current database column order.
Answer: The behaviors callers need and the invariants those operations must preserve.
2. When can public immutable fields be reasonable?
Answer: When the transparent representation is intentionally the stable contract and exposes no invalid mutable state.

Keep building

Authoritative references

Make the idea retrievable.

Study this concept with spaced repetition, next to the commands where you use it.

Get Terminaster