SOLID I: Single Responsibility & Open/Closed
Clean Code, SOLID & Refactoring
Chapter 6 · SOLID I: Single Responsibility & Open/Closed
SOLID gives names to disciplines this course has already been practicing informally. Chapter 4's Large Class and Chapter 5's Feature Envy were both early instances of the first principle here; Chapter 1's platinum-tier example was an early instance of the second. This chapter verifies both principles directly, at their sharpest.
Single Responsibility: "One Reason to Change," Tested Concretely
calculate_total_pay temporarily broken (a real, common mid-refactor state): calling print_report() — which is supposed to be testing formatting, nothing else — correctly fails with RuntimeError: pay calculation logic is broken mid-refactor. There is no way to verify the report's own formatting is correct while this class's other responsibility is broken, because the two are bundled into one object with no boundary between them.
PayrollReportFormatter().format_report(9999.99) — a fake total, with PayCalculator never even constructed — correctly returns "Total: $9999.99". And PayCalculator's own source, captured via inspect.getsource() before and after using the formatter: byte-identical, True. The two responsibilities can now genuinely be verified, changed, and reasoned about independently.
Open/Closed: Extended Without Touching a Single Existing Line
PlatinumDiscount(DiscountStrategy) class — a genuine new discount type — and re-capturing every existing class's own source afterward: PricingEngine unchanged (True), DiscountStrategy unchanged (True), NoDiscount unchanged (True), GoldDiscount unchanged (True). Nothing that already existed was opened, edited, or even needed to be re-read.
PricingEngine(PlatinumDiscount()).calculate(100) correctly returns 70.0 — PricingEngine's own code never changed, yet it correctly applies a discount type that didn't exist when it was written. This is "closed for modification" verified literally, not just "unlikely to need a change."
PricingEngine stays genuinely untouched by new discount types — is what Open/Closed means: Strategy (and, for a different kind of extension, Decorator, Design Patterns Chapter 5) are concrete design patterns that exist specifically to satisfy this principle. SOLID names the goal; the patterns are working implementations of it.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
| A bundled class's own formatter untestable while the calculator is broken | Chapter 4's own Large Class finding (3 concerns in 1 class) — the exact same problem, now measured by testability instead of concern-counting |
| 4 existing classes verified byte-identical after a real extension | Chapter 1's own platinum-tier example — the identical principle, verified more completely (whole classes, not one dict line) |
| Strategy named directly as an OCP implementation | Design Patterns Chapter 5's Decorator — a second, equally valid way to satisfy Open/Closed, covered next in Chapter 8's refactoring catalog |
Hands-On Exercises
Add a second method to PayrollReportFormatter, format_summary_line(total, employee_count), following the same pattern as format_report. Verify it can be tested with two fake values, with zero dependency on PayCalculator, exactly like this chapter's own format_report.
Add a second new discount type, SilverDiscount (15% off), to this chapter's own DiscountStrategy hierarchy. Verify every one of the now-five existing classes (including PlatinumDiscount from this chapter) stays byte-identical, and verify PricingEngine correctly applies it.
Using this chapter's own two verified findings, explain why satisfying Open/Closed for DiscountStrategy required Single Responsibility to already be true for PricingEngine — what would have gone wrong extending PricingEngine with a new discount type if it had been bundled with unrelated responsibilities the way PayrollReportBad was?
Chapter 6 Quick Reference
- Single Responsibility, verified: a bundled formatter genuinely couldn't be tested while its class's other responsibility was broken; a split-out formatter tested correctly against a fake value with zero dependency
- Open/Closed, verified: all 4 existing classes stayed byte-identical after a real extension, with the completely unmodified
PricingEnginecorrectly applying the new discount - The connection to Design Patterns: Strategy and Decorator are concrete, working implementations of Open/Closed — this chapter measured the principle; that course built the mechanism
- Next chapter: SOLID II — Liskov Substitution, Interface Segregation & Dependency Inversion