How do you design getters and setters so that they protect invariants instead of just exposing the fields?
Start from the rules the class promises, not from the fields it happens to have. If the promise is that a balance is never negative, then every path that writes a balance has to enforce it. A setter that validates is one such path. A deposit method that rejects non-positive amounts is another. Both funnel into the same private state, so there is no back door.
public class Main {
public static void main(String[] args) {
Account a = new Account("Ada", 100.0);
a.setBalance(250.0);
System.out.println("balance = " + a.balance());
try {
a.setBalance(-5.0);
} catch (IllegalArgumentException e) {
System.out.println("rejected: " + e.getMessage());
}
System.out.println("balance = " + a.balance());
}
}
class Account {
private final String owner;
private double balance;
Account(String owner, double balance) { this.owner = owner; setBalance(balance); }
void setBalance(double value) {
if (value < 0) {
throw new IllegalArgumentException("negative balance");
}
balance = value;
}
double balance() { return balance; }
void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("deposit must be positive");
}
balance += amount;
}
}The output shows the difference between enforcing and assuming. The valid write succeeds and the balance becomes 250.0. The invalid write is refused with a clear message, and a final read confirms the balance is still 250.0, so the rejected value never touched the object.
Notice the constructor also routes through the setter. That is deliberate, and it is the detail interviewers like to see: validation that lives in one place applies to every way of creating the object, including deserialisation and factory methods. If the constructor had assigned the field directly, the rule would have a gap in it.
The stronger move, when you can, is to remove the setter altogether. If balance should only ever increase, then a setBalance method is a hole in the design that a future caller will find and use. Offer deposit and withdraw, and there is no operation that can put the account into an invalid state.