How does information hiding reduce the cost of change, and what happens to callers when an implementation detail changes?
The mechanism is dependency direction. When a caller can only reach a narrow contract, it does not compile against the parts of the implementation that are free to change. So when the implementation changes, the caller is untouched and does not even need to be recompiled in a meaningful sense. The cost of the change stays inside the class that owns the detail.
public class Main {
public static void main(String[] args) {
Report r = new Report();
r.addLine("alpha");
r.addLine("beta");
System.out.println(r.render());
System.out.println("lines = " + r.lineCount());
}
}
class Report {
private final java.util.List<String> lines = new java.util.ArrayList<>();
void addLine(String line) { lines.add(line); }
int lineCount() { return lines.size(); }
String render() {
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append("- ").append(line).append("\n");
}
return sb.toString();
}
}The report class is a small illustration. The internal list is private, and the caller can only add lines, count them and ask for a rendered string. Inside render, the implementation could switch from a list to an array, add indentation, or escape markup, and no caller would notice, because no caller ever saw the list.
The second benefit is about people rather than machines. Encapsulation means that a developer reading a caller does not have to read the implementation to understand what is happening, because the caller's view of the component is the contract and nothing more. That is why large systems stay navigable.
The third benefit is that it lets you change your mind. If the storage choice was hidden behind an interface, you can change it late in development, which is exactly when you have learned the most about the requirements. If it leaked, that same late change becomes a sweeping refactor.