LESSON 1.2
Encapsulation protects a rule
Keep a shopping-cart quantity valid, and avoid leaking a mutable reference around the boundary.
6 min read
A checkout page validates that a quantity is positive. Then a promotion job writes quantity = -2 directly, bypassing the page. Validation in one caller is not an object invariant. Encapsulation puts the rule next to the state and makes every supported change pass through it.
The problem
const line = { product: "tea", quantity: 1 };
line.quantity = -2; // Another caller can bypass the checkout form.The object boundary
Encapsulation means controlling access to state through a useful contract. It is not merely adding getters and setters to every field. A public setQuantity method that accepts any value has exactly the same problem as a public field. Prefer an operation that says what the caller is trying to do, such as adding a positive number of units. The operation checks its preconditions and preserves the invariant before returning.
The cart line below stores a product identity and a positive integer quantity. Construction validates the initial quantity; add validates the change. A snapshot gives the caller an immutable description rather than the live mutable state. The user interface can render that description without gaining permission to mutate the line. This is especially important when an object owns a list or a map: returning the internal collection can silently reopen the boundary you just closed.
Keep the scope of the rule clear. This object prevents negative quantities in memory. It does not reserve inventory, authorize a purchase, or make two concurrent database updates atomic. Those rules require other boundaries. A good object API makes the local guarantee precise, then exposes enough information for a higher-level transaction to enforce the wider guarantee.
In code
Both versions run the same small example. Java uses a single Example class; save it as Example.java.
When the boundary earns its place
Use this boundary for state with rules, not for hiding arbitrary implementation details that nobody needs to change. Immutable values often need no mutation methods at all. Defensive copies cost allocation and can be shallow: a copied list of mutable products still shares those products. Decide whether to return immutable values, copies, or a narrow read-only view, and explain what each choice actually protects.
JavaScript and Java
JavaScript #private fields are enforced by the language; an underscore is only a naming convention. Java private restricts field access, but does not make the object immutable or thread-safe. A Java record is shallowly immutable: a record containing a mutable list still needs a defensive copy. JavaScript Object.freeze is also shallow. The examples use strings and numbers so their snapshots have no hidden mutable children.
Try it
Shopping Cart Domain LLD
Identify the objects and state the rule each one owns before adding a pattern.