Here is a class that leaks its internals. What is wrong with it, and how would you fix it without breaking the way it is used?
Two things are wrong, and both are about the class losing control of its own state. The lines field is public, so any caller can add, remove or clear it, and the invoice has no idea. The taxRate field is public and mutable, so a caller can set a rate that breaks any pricing rule the invoice is supposed to enforce. The class computes a total, but it cannot promise the total is right, because its inputs are editable from outside.
public class Main {
public static void main(String[] args) {
Invoice invoice = new Invoice();
invoice.lines.add("Widget");
invoice.lines.add("Gadget");
invoice.taxRate = 0.2;
System.out.println(invoice.total());
}
}
class Invoice {
final java.util.List<String> lines = new java.util.ArrayList<>();
double taxRate = 0.0;
double total() {
double sum = 0;
for (String line : lines) {
sum += 10.0;
}
return sum * (1 + taxRate);
}
}The output shows the price being assembled from outside. The caller adds the lines and sets the tax rate itself, and the invoice faithfully computes 24.0 from that. But nothing stops a caller setting the rate to a negative number, or clearing the list and then asking for a total, or adding a line and never paying for it in a consistent way.
The fix follows the shape of the class. Make both fields private, replace direct list manipulation with an addLine method, and move the tax rate into the constructor so it is set once and validated once. If the rate genuinely varies, give it a method that validates the value. The call sites then read invoice.addLine("Widget") and new Invoice(0.2), which is barely different from what they did before, so the change is cheap.
The general lesson to draw out is that a public mutable field is the same as no encapsulation at all, and a public mutable collection is worse because the damage arrives through the back door.