LESSON 2.1
Single responsibility
A class that does too much is the class that breaks everything at once. Give each class one reason to change.
7 min read
An OrderProcessor class that sends emails, applies discounts and saves to a database sounds efficient. In practice it means every change to email templates forces a re-test of the discount engine, and every database schema migration touches both. It also means three different teams all edit the same file.
The problem
class OrderProcessor {
process(order) {
// discount logic: 10 lines
const discount = order.total > 100 ? 0.1 : 0;
const final = order.total * (1 - discount);
// persistence: 5 lines
db.save({ ...order, final });
// email: 5 lines
mailer.send(order.email, `Your total is ${final}`);
return final;
}
}
// Changing the email template means re-testing discount logic and db writes.Three different reasons to change live in one method. A discount rule change, a database migration and an email template update all touch the same class.
The rule
Single Responsibility Principle: a class should have exactly one reason to change. That reason is owned by one actor — a team, a domain or a business rule.
The most useful way to apply SRP is by asking: who will ask me to change this? If the answer is "the billing team and the marketing team", split the class.
In code
Separate the three concerns into three classes. The coordinator calls them in sequence.
When to split and when not to
Splitting too eagerly creates an explosion of tiny classes that are hard to read together. A good heuristic: split when the two concerns change at different rates or for different people. A User class that stores a name and validates the name format is not a violation — the validation is part of the same business rule.
The cost of SRP is one extra class and one extra seam per responsibility. That seam is exactly what makes unit testing easy: inject a fake Mailer and test the discount logic without setting up an SMTP server.
Where you have already seen it
Node's http module separates IncomingMessage (request parsing) from ServerResponse (response writing). Express separates routing from middleware. React separates state logic (a hook) from rendering (a component).
Try it
Shopping Cart Domain LLD
Split the cart into a pricing policy and a line-item collection before adding checkout logic.