From grimoire
Hides a class's internal state and protects invariants by exposing behavior instead of data. Use when callers reach into fields directly, when setters violate invariants, or when internal data structures leak into the public API.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-encapsulationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Hide a class's internal state and protect its invariants by exposing behavior, not data.
Hide a class's internal state and protect its invariants by exposing behavior, not data.
Adopted by: Java language specification (fields default to package-private), C++ (access specifiers as language construct), Google Java Style Guide (no public fields except constants), Python (convention of _ prefixes), Swift (access control built into the language spec).
Impact: A Carnegie Mellon study of defect data across open-source Java projects found that classes with public mutable fields had 3–5× higher defect rates than classes with private fields and controlled accessors — because invariant violations could originate anywhere in the codebase. Holub ("Holub on Patterns") measured that the majority of getter/setter pairs in enterprise Java codebases are never called outside the class's own package, meaning the exposure is gratuitous and the coupling cost is real with zero benefit.
Why best: Anemic domain models (pure data bags + external service classes) produce the same coupling problem as public fields while appearing more OOP-like. The canonical alternative — indiscriminate getters/setters for every field — provides the syntax of encapsulation without the substance: callers can still drive the object into illegal states via setters. True encapsulation exposes behavior the object performs, not data the caller manipulates.
Sources: Booch, "Object-Oriented Analysis and Design" (Addison-Wesley, 1994); West, "Object Thinking" (Microsoft Press, 2004); Holub, "Holub on Patterns" (Apress, 2004); Google Java Style Guide
No field should be public or protected unless it is a named constant (static final / const).
// Bad — caller can set balance to negative
public class Account {
public double balance;
}
// Good — internal state is hidden
public class Account {
private double balance;
}
Ask: "what does the object do?" — not "what data does it have?" Methods should be commands or queries about the object's role, not mirrors of its fields.
// Bad — exposes internal representation; caller must understand internals
public double getBalance() { return balance; }
public void setBalance(double balance) { this.balance = balance; }
// Good — behavior the object performs
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
this.balance += amount;
}
public void withdraw(double amount) {
if (amount > balance) throw new InsufficientFundsException();
this.balance -= amount;
}
public boolean canAfford(double amount) {
return balance >= amount;
}
Every method that mutates state must check invariants before applying the change. The object is always in a valid state after every method call.
// Bad — invariant (start < end) can be violated from outside
public class DateRange {
public LocalDate start;
public LocalDate end;
}
// Good — invariant enforced in constructor and any mutation
public class DateRange {
private final LocalDate start;
private final LocalDate end;
public DateRange(LocalDate start, LocalDate end) {
if (!start.isBefore(end)) throw new IllegalArgumentException("start must be before end");
this.start = start;
this.end = end;
}
public boolean contains(LocalDate date) {
return !date.isBefore(start) && !date.isAfter(end);
}
}
Returning a direct reference to an internal collection lets callers mutate it outside the class's control.
// Bad — caller can clear the internal list
public List<Order> getOrders() { return orders; }
// Good — return an unmodifiable view
public List<Order> getOrders() {
return Collections.unmodifiableList(orders);
}
For each getter or setter, ask:
If a getter exists only so another class can compute something with the data — that computation belongs inside this class.
Scenario: A User class has public email and role fields; a service modifies them directly to perform a role promotion. The email is changed to an invalid format in a bug. Moving email to private with a setEmail(String) that validates format, and promoteToAdmin() as a named method on User, eliminates both the invalid-state bug and the scattered promotion logic.
Scenario: A ShoppingCart exposes List<Item> items as a public field. A promotions engine removes items directly to apply discounts. The removal breaks the cart's totalPrice() cache. Making items private and exposing addItem(), removeItem(), and applyDiscount() methods lets the cart maintain its cache invariant.
Scenario: A Config class has all fields public for easy construction in tests. A test sets maxRetries = -1 and the retry loop runs forever. Making fields private with a builder that validates ranges eliminates the illegal-state path in both tests and production.
setX(x) { this.x = x; } with no validation is public field access with extra syntax. Add a validation check or remove the setter.this.items directly — callers mutate internal collections without the object knowing. Return unmodifiableList(items) or a copy.protected fields in a parent class are accessible to all subclasses, breaking encapsulation across the inheritance hierarchy. Expose via protected methods instead.getConnection() that returns a MySQLConnection leaks the implementation. Return an interface type.Point(x, y) or a JSON response DTO whose sole job is carrying data across a boundary; encapsulation overhead adds no value when there is no invariant to protectnpx claudepluginhub jeffreytse/grimoire --plugin grimoireImplements OOP encapsulation in Java and Python with private fields, validated getters/setters, defensive copying, and access control for robust classes.
Analyzes PHP code for encapsulation violations like public mutable state, getter/setter abuse, Tell Don't Ask breaches, exposed internals, and anemic domain models. Reviews domain entities and aggregates.
Identifies and fixes Law of Demeter violations (train wrecks) where code chains method calls through intermediate objects, reducing coupling and improving maintainability.