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 -= amountwithdraw 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 fieldsNot 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.
2. When can public immutable fields be reasonable?
Keep building