Encapsulation restricts direct access to internal state and exposes controlled operations that preserve consistency. It is useful when a type has real rules to protect, but it is not free: routine data objects can acquire constructors, accessors, setters, equality code, string representations, and builders until the supporting machinery obscures the data’s meaning. section 4.1.2 section 4.4
Use encapsulation to separate a stable, high-level action from details that may vary. A report-generation operation should tell the story—generate this report—without embedding the format-specific implementation. A shared interface can encapsulate interchangeable PDF, CSV, Excel, or later HTML behaviors, leaving orchestration small and allowing formats to grow independently. section 3.2.4
Hide report formats behind an operation
A conditional dispatcher couples report generation to every known format: each new format changes the method. Replacing format checks with a ReportGenerator interface moves generation details into implementations. The caller supplies a generator, and orchestration becomes report.generate(data). This is encapsulation of interchangeable behavior behind a common operation, aligned with the Strategy Pattern. 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));
}
The conditional version makes the coupling visible: format selection and format-specific generation occupy the same method, so extending formats expands its control flow. section 3.2.4
For plain data carriers, distinguish protection from ceremony. A Person with many optional constructor parameters can become unreadable and error prone; constructor overloads multiply quickly, while a builder makes selected fields explicit. Yet even a fully encapsulated two-field DTO may contain more boilerplate than domain meaning. Prefer small, precisely named data carriers and use validation where it has value rather than adding layers by habit. section 4.1.2 section 4.4
Choose the boundary by asking what must remain stable:
- Hide implementation variation when clients need one enduring action but implementations can change independently. section 3.2.4
- Protect mutable state when uncontrolled changes could create inconsistency; account for the defensive and accessor code this requires. section 4.1.2
- Keep simple data visible and meaningful when boilerplate would outweigh its purpose; use concise immutable carriers where available. section 4.4













