SRP(단일 책임 원칙)는 객체 지향 설계를 깔끔하게 만드는 첫걸음이랍니다.

SRP 학습 계획 🚀
- 단일 책임 원칙(SRP)이란?: SRP가 무엇인지 핵심 개념을 알아봐요. '책임'이 대체 뭘 의미하는지 확실히 짚고 넘어갈 거예요.
- SRP는 왜 중요할까?: SRP를 지키면 어떤 점이 좋은지, 왜 프로그래머들이 강조하는지 그 이유를 파헤쳐 봐요.
- 실전 예제로 감 잡기: 간단한 코드를 통해 SRP를 지켰을 때와 안 지켰을 때가 어떻게 다른지 비교하며 확실하게 감을 잡아봐요.
1. 단일 책임 원칙(SRP)이란?
단일 책임 원칙(Single Responsibility Principle, SRP)은 아주 간단한 아이디어에서 출발해요. 바로 "클래스는 단 하나의 책임만 가져야 한다"는 원칙이죠.
혹시 '맥가이버 칼' 아시나요? 칼, 드라이버, 톱, 가위가 다 들어있어서 만능처럼 보이죠. 하지만 만약 톱날이 무뎌지면 칼 전체를 수리 맡기거나 바꿔야 할 수도 있어요. SRP는 이런 상황을 피하자는 거예요. 클래스를 '맥가이버 칼'처럼 만들지 말고, 각자 자기 역할만 잘하는 '칼', '드라이버', '톱'으로 나누자는 거죠.

여기서 말하는 '책임'은 '변경되어야 하는 이유'라고 생각하면 쉬워요. 즉, "클래스를 수정해야 할 이유는 단 하나뿐이어야 한다"는 뜻이에요.
예를 들어, 직원(Employee)이라는 클래스가 있다고 상상해 보세요. 이 클래스가 직원의 월급을 계산하는 책임(calculatePay())과 근무 시간을 기록하는 책임(reportHours()), 그리고 데이터베이스에 직원 정보를 저장하는 책임(save())을 모두 가지고 있다면 어떨까요?
- 세금 정책이 바뀌면 월급 계산 로직을 바꿔야 하고,
- 근무 시간 보고 양식이 바뀌면 기록 로직을 바꿔야 하고,
- 데이터베이스 시스템이 바뀌면 저장 로직을 바꿔야 해요.
이렇게 세 가지 다른 이유로 직원 클래스를 수정해야 한다면, 이 클래스는 SRP를 위반한 거예요. 하나의 기능 수정이 다른 기능에 의도치 않은 버그를 만들 수도 있고요.
퀴즈
여기에 이메일을 보내는 역할을 하는 EmailSender 클래스가 있습니다. 이 클래스는 다음과 같은 세 가지 일을 한답니다.
- 사용자 이메일 주소 확인: 보내려는 이메일 주소가 올바른 형식인지(aaa@bbb.com) 확인합니다.
- 이메일 내용 조합: 제목과 본문을 합쳐서 최종 이메일 내용을 만듭니다.
- 이메일 서버로 전송: 만들어진 이메일을 실제 서버를 통해 발송합니다.
이 EmailSender 클래스는 단일 책임 원칙(SRP)을 잘 지키고 있을까요? 왜 그렇게 생각하시나요?
정답 : SRP 위반
'이메일 주소 확인', '내용 조합', '서버로 전송'은 서로 다른 책임이에요.
- 이메일 주소 형식을 검증하는 정책이 바뀌면 클래스를 수정해야 하고,
- 이메일 내용을 만드는 방식(예: HTML 템플릿 추가)이 바뀌어도 수정해야 하고,
- 이메일 발송 서버의 주소나 프로토콜이 바뀌어도 수정해야 하죠.
이렇게 수정해야 할 이유가 여러 개라는 것이 바로 SRP를 위반했다는 강력한 신호랍니다. 하나의 기능을 고치려다 다른 기능을 망가뜨릴 위험도 커지고요.


2. SRP는 왜 중요할까? (SRP의 장점)
우리가 첫 번째 활동에서 봤던 복잡한 EmailSender 클래스를 떠올려보세요. SRP를 적용해서 '주소 검증', '내용 조합', '실제 발송' 클래스로 나눈다면 다음과 같은 장점들이 생긴답니다.
가독성이 높아지고 이해하기 쉬워져요 👀
클래스 이름만 봐도 "아, 이 클래스는 이메일 주소 검증만 하겠구나!" 하고 역할을 바로 파악할 수 있어요. 코드를 처음 보는 사람도 훨씬 쉽게 이해할 수 있죠. 마치 서랍마다 '양말', '속옷', '수건'이라고 이름표를 붙여놓는 것과 같아요.
유지보수가 편해져요 🔧
이메일 서버 전송 방식만 바꾸고 싶다면 '실제 발송' 클래스만 열어보면 돼요! 다른 코드를 건드릴 필요가 없으니 변경으로 인한 실수를 줄일 수 있고(안정성 UP!), 필요한 부분만 쏙쏙 고칠 수 있어서 수정 속도도 빨라져요(생산성 UP!).
재사용성이 올라가요 ♻️
'이메일 주소 검증' 기능은 이메일 보내는 곳 말고도 회원가입이나 내 정보 수정 페이지에서도 필요하겠죠? 이렇게 클래스를 책임 단위로 잘 나눠두면, 필요한 곳에 부품처럼 가져다 쓸 수 있어요. 범용적인 부품은 여기저기 쓸모가 많은 법이죠!
테스트하기 쉬워져요 ✅
책임이 하나인 클래스는 테스트할 범위도 명확해요. '이메일 주소 검증' 클래스는 "잘못된 이메일 주소를 잘 걸러내는가?"만 집중해서 테스트하면 되니까요. 책임이 여러 개 섞여있을 때보다 훨씬 간단하고 정확한 테스트가 가능해져요.
요약하자면, SRP는 코드를 더 깔끔하고, 유연하고, 튼튼하게 만들어주는 아주 중요한 기본 원칙이랍니다!
3. 실전 예제
사용자에 대한 간단한 보고서를 만들고 출력하는 상황을 가정해 볼게요.
❌ Before: SRP를 지키지 않은 코드
하나의 클래스가 데이터 처리, 내용 형식 지정, 출력까지 모든 것을 담당하고 있어요.

class BadReport:
def __init__(self, user_id):
self.user_id = user_id
self.user_name = ""
def get_user_data(self):
# 1. 데이터베이스에서 사용자 정보를 가져오는 책임
print(f"{self.user_id} 사용자의 데이터를 DB에서 가져옵니다...")
self.user_name = "홍길동" # 예시 데이터
def format_report_as_html(self):
# 2. 보고서를 HTML 형식으로 만드는 책임
print("보고서를 HTML 형식으로 변환합니다...")
return f"<h1>{self.user_name}님의 보고서</h1>"
def print_report(self):
# 3. 보고서를 프린터로 출력하는 책임
self.get_user_data()
report_html = self.format_report_as_html()
print("프린터로 보고서를 출력합니다.")
print(report_html)
# 실행
report = BadReport("user123")
report.print_report()
문제점: 이 BadReport 클래스는 'DB 접근 방식', '보고서 디자인', '출력 방식'이라는 세 가지의 서로 다른 변경 이유를 가지고 있어요. 보고서 디자인만 바꾸고 싶은데 DB 관련 코드까지 신경 써야 하는 상황이죠.
✅ After: SRP를 적용한 코드
각자의 책임만 담당하는 세 개의 클래스로 분리했어요.

# 1. 데이터 처리 책임
class UserRepository:
def get_user_name(self, user_id):
print(f"{user_id} 사용자의 데이터를 DB에서 가져옵니다...")
return "홍길동"
# 2. 보고서 형식 책임
class ReportFormatter:
def format_as_html(self, user_name):
print("보고서를 HTML 형식으로 변환합니다...")
return f"<h1>{user_name}님의 보고서</h1>"
# 3. 보고서 출력 책임
class ReportPrinter:
def __init__(self, repository, formatter):
self.repository = repository
self.formatter = formatter
def print_report(self, user_id):
user_name = self.repository.get_user_name(user_id)
formatted_report = self.formatter.format_as_html(user_name)
print("프린터로 보고서를 출력합니다.")
print(formatted_report)
# 실행: 부품(클래스)들을 조립해서 사용!
repo = UserRepository()
formatter = ReportFormatter()
report_printer = ReportPrinter(repo, formatter)
report_printer.print_report("user123")
개선된 점:
- 데이터베이스가 바뀌면 UserRepository만 수정하면 돼요.
- 보고서 디자인을 JSON이나 PDF로 바꾸고 싶다면 ReportFormatter만 수정하거나 새로 만들면 되죠.
- 출력 방식을 이메일로 바꾸고 싶다면 ReportPrinter만 보면 되고요.
이제 각 클래스가 한 가지 일에만 집중하니 훨씬 깔끔하고, 부품처럼 갈아 끼우기도 쉬워졌어요!
✨ 퀴즈 ✨
온라인 쇼핑몰의 상품(Product) 클래스를 만든다고 상상해 보세요. 이 클래스가 단일 책임 원칙(SRP)을 가장 잘 지키려면, 다음 중 어떤 책임을 가져야 할까요?
- 상품의 가격, 재고를 관리하고, 상품을 웹페이지에 HTML로 표시하는 기능
- 상품의 가격, 이름, 설명 등 순수한 데이터 속성을 관리하는 기능
- 상품의 재고 수량을 확인하고, 재고가 부족하면 공급처에 자동으로 재주문을 넣는 기능
- 사용자의 장바구니에 상품을 추가하고, 해당 상품의 결제를 처리하는 기능
정답 : 2번
왜 2번이 정답일까요?
상품 클래스가 오직 상품의 순수한 데이터(이름, 가격 등)를 관리하는 책임만 가지기 때문이에요. 이 클래스를 수정할 이유는 '상품의 데이터 속성이 변경될 때' 단 하나뿐이죠.
다른 보기들은 왜 틀렸을까요?
- 1번: '데이터 관리'와 '웹페이지 표시'라는 두 가지 책임이 섞여 있어요.
- 3번: '재고 확인'과 '공급처 재주문'은 서로 다른 비즈니스 로직이에요.
- 4번: '장바구니'와 '결제'는 완전히 분리되어야 하는 매우 큰 책임들이죠.
요약
- SRP란?: 클래스는 변경되어야 할 이유를 단 하나만 가져야 한다.
- SRP의 장점: 코드를 깔끔하고, 유연하며, 튼튼하게 만든다.
'소프트웨어공학' 카테고리의 다른 글
| CQRS(Command Query Responsibility Segregation) 패턴 (0) | 2026.03.02 |
|---|---|
| [SOLID] ISP 원칙 (Interface Segregation Principle) (1) | 2025.10.07 |
| [SOLID] LSP 원칙 (Liskov Substitution Principle) (0) | 2025.10.03 |
| [SOLID] DIP (Dependencies Inversion Principles) (0) | 2025.10.03 |
| [SOLID] OCP (Open-Closed Principle) (0) | 2025.10.03 |