One Reason to Change
The Single Responsibility Principle is the S in SOLID, five rules for designing code that stays easy to change. It says: a type should have one job — and therefore only one reason to change.
When one type saves orders and sends emails, it changes when the storage changes and when the email text changes. Two jobs, two reasons, twice the risk of breaking one thing while touching the other.
In Python the rule applies to classes, their methods, and modules: keep each one focused on a single task.
Too Many Jobs in One Class
This order processor works — but it stores the order, writes the confirmation text, and "sends" it, all in one method. Testing the email text alone is impossible, and reusing the storage part elsewhere means dragging the email code along.
One class that does everything
process() mixes two jobs: storing and notifying. Any change to either one means editing — and re-testing — this same class.
Output: complete Python program
class OrderProcessor:
# process does two jobs: storing AND notifying.
def process(self, order, customer):
print("saving order to storage:", order)
print("sending email to", customer + ":")
print(" Thank you! Your order", order, "was received.")
p = OrderProcessor()
p.process("#1042 (2 books)", "ana@example.com")
One Job per Class
The fix: one class per job. OrderStore only stores, EmailSender only sends. A small function coordinates them.
The output is the same — but now each part can be changed, tested, and reused on its own.
Split into two classes
Same behavior, separated: the store knows nothing about emails, the sender knows nothing about storage. The process_order function just connects them.
Output: complete Python program
# OrderStore has one job: storing orders.
class OrderStore:
def save(self, order):
print("saving order to storage:", order)
# EmailSender has one job: sending emails.
class EmailSender:
def send_confirmation(self, customer, order):
print("sending email to", customer + ":")
print(" Thank you! Your order", order, "was received.")
def process_order(order, customer):
store = OrderStore()
sender = EmailSender()
store.save(order)
sender.send_confirmation(customer, order)
process_order("#1042 (2 books)", "ana@example.com")
The payoff: reuse without fear
Because the sender is its own class, it sends a shipping notice too — no storage code involved. And the store archives an order without composing a single email. Each class grew inside its one job.
Output: complete Python program
class OrderStore:
def save(self, order):
print("saving order to storage:", order)
def archive(self, order):
print("moving order to the archive:", order)
class EmailSender:
def send_confirmation(self, customer, order):
print("email to", customer + ": order", order, "was received")
def send_shipping_notice(self, customer, order):
print("email to", customer + ": order", order, "was shipped")
store = OrderStore()
sender = EmailSender()
# Each class grows inside its own job.
sender.send_shipping_notice("ana@example.com", "#1042")
store.archive("#0997")