0 XP · 0 scenes

Single Responsibility

A class should have one reason to change
The problem

One Invoice class calculates the total, prints the text and saves to the database.

The idea

Split by reason to change.

Reach for it when

A class mixes maths, display and storage (or any two unrelated jobs)

1

The picture from life

In a small restaurant the cook cooks and the cashier handles the bill. If one person did both, a change in the menu and a change in the tax rule would both land on the same person, and a mistake in one would spoil the other. Give each job to its own person and a change touches only one of them.

2

The problem

What goes wrong when you do not use it.

One Invoice class calculates the total, prints the text and saves to the database. Change the print layout and you risk breaking the maths. Change the database and you reopen the printing code. Every request touches the same fragile file.

✗ Before
Tax teamDesign teamDatabase teamInvoiceitemstotal() ← tax ruleto_text() ← print layoutsave_to_db() ← storagechanges total()changes to_text()changes save
  • calls / uses
One class, three reasons to change. Every team edits the same file.
Reach for it when
  • A class mixes maths, display and storage (or any two unrelated jobs)
  • You say "and" when describing what the class does
  • A small change makes you retest unrelated features
3

The idea

How the pattern fixes it.

Split by reason to change. Ask "who would ask me to change this?": the finance team (maths), the designer (layout), the DBA (storage). Each answer becomes its own small class.

✓ After
Invoiceitemstotal()InvoicePrintertext(invoice)InvoiceStoresave(invoice)readsstores
  • calls / uses
Each class has one job, so each change touches only one class.

Who is who

Invoice
Holds items and knows the total. Changes only when pricing rules change.
InvoicePrinter
Turns an invoice into text. Changes only when the layout changes.
InvoiceStore
Saves invoices. Changes only when storage changes.

Build it in steps

  1. 1List what the class does in plain words.
  2. 2Group the jobs by who would ask for a change.
  3. 3Move each group into its own class.
  4. 4Let a small caller wire them together.
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

ClientInvoiceStoreInvoicePrinterInvoice1save(invoice)2text(invoice)3total()41005'pen | book | TOTAL 100'
Step 1/5

Storing is the store's job. The invoice does not know how it is saved.

save(invoice)
5

The code

Python and Java. Try writing the classes yourself from the diagram first.

class Invoice:
    def __init__(self, items):
        self.items = items                         # list of (name, price)

    def total(self):
        return sum(price for _, price in self.items)


class InvoicePrinter:
    def text(self, invoice):
        names = " | ".join(name for name, _ in invoice.items)
        return f"{names} | TOTAL {invoice.total()}"


class InvoiceStore:
    def __init__(self):
        self.saved = []

    def save(self, invoice):
        self.saved.append(invoice)


def demo():
    invoice = Invoice([("pen", 10), ("book", 90)])
    store = InvoiceStore()
    store.save(invoice)
    return InvoicePrinter().text(invoice), len(store.saved)

What you gain

  • A change stays inside one small class
  • Each class is easy to test alone
  • Teams can work on different classes without conflicts

What it costs

  • More classes and files to navigate
  • Over-splitting a tiny class into many pieces just adds noise

You have already met it in

  • A Controller / Service / Repository split in web frameworks
  • Separating a logger from the code that logs

Often mixed up with

  • FacadeFacade hides many classes behind one door; SRP is about splitting one overloaded class apart.
6

Make it stick

Take a class you wrote that both calculates and prints. Split it in two and make the printer swappable for a new format.

Confidence