design·lab

S · SOLID

Single Responsibility Principle

A class should have only one reason to change. When a class juggles unrelated jobs, a change to one job risks breaking the others.

✗ The problem

One class, three jobs

This Report computes totals, formats HTML, and saves to disk. Click a request below — watch which lines it forces you to touch.

class Report {
  calculateTotals(rows) { /* business logic */ }
  renderHtml(totals)    { /* presentation  */ }
  saveToDisk(html)      { /* persistence   */ }
}
Report
calc · format · save
Three unrelated triggers — finance, design, infra — all reopen the same class. A tweak to the HTML can break totals. That's the smell.
✓ The refactor

Split by responsibility

Give each job its own class. Each now has exactly one reason to change.

// class = the role (noun) · method = the action (verb)
class TotalsCalculator { compute(rows)   {…} }
class ReportRenderer   { toHtml(totals) {…} }
class FileStore        { save(path, data) {…} }
TotalsCalculator
business logic
ReportRenderer
presentation
FileStore
persistence
Methods don't repeat the class name (Calculator.calculate "stutters"). A clean verb — compute / toHtml / save — reads like a swappable contract: the caller cares what it does, not what it's called.
✓ Compose, don't cram

A thin coordinator wires them together

The pieces stay independent; a small function orchestrates the flow.

function buildReport(rows) {
  const totals = calculator.compute(rows);
  const html   = renderer.toHtml(totals);
  store.save('report.html', html);
}
TotalsCalculator
ReportRenderer
FileStore
Now a rebrand touches only ReportRenderer. A storage change touches only FileStore. Nothing else moves.
✓ Takeaway

Spot it, split it

  • Smell: the word "and" in a class description ("formats and saves").
  • Test: can two different stakeholders demand changes to the same class? If yes, split.
  • Benefit: smaller blast radius, easier tests, safer reuse.
  • Careful: don't over-split — cohesive logic that changes together should stay together.
Next: Open/Closed → — extend behavior without editing old code.