Back to the map

Module 02 · SOLID and friends · Lesson 1 of 7

Single responsibility

A class should have one reason to change, meaning it answers to one kind of requirement or one group of people. How to spot a class serving several masters, how to split it, and how to avoid splitting so far that the design turns to dust.

CoreMust-knowJunior 11 min Updated Nov 2026

Why it matters

When one class calculates invoices, formats them as PDF and saves them to the database, a change requested by finance (a new tax rule) can break what operations depends on (the export format), and every change risks the other two jobs. Merge conflicts cluster in that file, tests need a database to check arithmetic, and nobody can reuse the calculation without dragging the rest along. The single responsibility principle is the first line of defence against classes that grow until they can no longer be changed safely.

The idea

The usual phrasing is "a class should have only one reason to change". The more useful reading: a class should answer to one actor, one source of requirement changes. Tax rules come from finance, layout from the people who read invoices, storage from engineering; code that serves all three will be changed for three unrelated reasons.

InvoicecalculateTotals · renderPdf · savefinance, design and storage changes all land heresplitInvoiceCalculatortax and totals (finance)InvoicePdfRendererlayout (document owners)InvoiceRepositorystorage (engineering)BillingServicecoordinates
One class answering to three actors splits into three classes, each changed for one kind of reason, coordinated by a thin service.

Spotting a violation

SymptomWhat it suggests
The class description needs 'and' several timesSeveral responsibilities
Groups of methods use disjoint groups of fieldsSeparate classes hiding in one
Imports span unrelated concerns: SQL, HTTP, PDF, emailBusiness logic tangled with infrastructure
Unrelated feature requests keep touching the same fileSeveral actors share one class
Testing one behaviour requires setting up another (a database to test arithmetic)Responsibilities that should be separable are not

Splitting well

Separate the policy (business rules) from the mechanisms (storage, transport, formatting). Keep each piece behind a small interface so the rules can be tested with plain objects and the mechanisms can change independently. Then let a thin coordinator, often a service, sequence the pieces for a use case.

final class InvoiceCalculator {                    // pure rules, no I/O
    Invoice calculate(Order order, TaxPolicy tax) { ... }
}
interface InvoiceRenderer { byte[] render(Invoice invoice); }      // PDF today, HTML tomorrow
interface InvoiceRepository { void save(Invoice invoice); }

final class BillingService {
    private final InvoiceCalculator calculator; private final InvoiceRenderer renderer;
    private final InvoiceRepository repository; private final TaxPolicy tax;
    // constructor omitted
    byte[] bill(Order order) {
        Invoice invoice = calculator.calculate(order, tax);
        repository.save(invoice);
        return renderer.render(invoice);
    }
}

Do not shatter the design

Taken too far, the principle produces a swarm of one-method classes whose behaviour can only be understood by reading ten files, which is worse than the original. A responsibility is a cohesive set of things that change together, not a single method. If two pieces always change together for the same reason, they belong together. Split when you can name two different actors or reasons, or when a test is painful because concerns are tangled; do not split speculatively.

Trade-offs

Following the principle buys isolated changes, simpler tests and reusable pieces, at the cost of more classes and some wiring. Over-applying it scatters cohesive logic. The judgement call is always the same question: do these parts change for the same reason, at the request of the same people?

In an interview

Apply it out loud when a class starts doing too much: "ParkingLot is getting pricing, ticket printing and spot allocation. Pricing changes with business rules and printing with hardware, so I will pull out a PricingPolicy and a TicketPrinter and keep ParkingLot responsible for spots." Naming the reason for each split is what scores.

Check yourself

A UserService validates sign-up data, hashes passwords, writes the user to the database, and sends a welcome email. A change to the email template breaks sign-ups in production. Which refactoring best follows the single responsibility principle?

Single responsibility · Backplane