Abstraction separates what a component does from how it does it. The goal is to expose the essential behavior while hiding implementation details that burden the reader. section 3.2.4
From format branching to a stable story
A report method that branches across PDF, CSV, and Excel formats makes the high-level story depend on every implementation. A switch reduces control-flow complexity, but the coupling remains. A ReportGenerator interface moves each format into its own implementation, so client code can simply call report.generate(data). Adding a format then extends the abstraction instead of enlarging the orchestration method. section 3.2.4
Listing 3.4 Generating sales reports
public void generateReport(ReportFormat format, List<Data> data) {
if (ReportFormat.PDF.equals(format)) {
generatePdf(data);
return;
} else if (ReportFormat.CSV.equals(format)) {
generateCsv(data);
return;
} else if (ReportFormat.EXCEL.equals(format)) {
generateExcel(data);
return; }
throw new UnsupportedOperationException(String.valueOf(format));
}
Inspect the original report-generation branching that motivates the abstraction boundary. section 3.2.4
Use abstraction as a complexity-management decision:
- Separate the system’s story from interchangeable implementations when the same behavior supports multiple formats or strategies. section 3.2.4
- Choose boundaries that reduce cognitive load; an abstraction that obscures logic or makes debugging harder has become counterproductive. section 3.3
- Contain unavoidable domain complexity in focused components or quarantine methods, then document the remaining knots clearly. section 3.2.6
Good abstraction is not concealment for its own sake. It is a deliberate boundary that keeps responsibilities navigable while preserving a clear path to the behavior being requested. section 3.2.4 section 3.3














