SRP(Single Responsibility Principle, 단일 책임 원칙)
**SRP(Single Responsibility Principle, 단일 책임 원칙)**은 SOLID 원칙 중 하나로, 객체 지향 설계에서 각 클래스나 모듈이 **단 하나의 책임(책임의 축)**만 가져야 한다는 원칙입니다.
SRP의 정의
단일 책임 원칙이란?
"클래스는 변경될 이유가 단 한 가지뿐이어야 한다."
즉, 하나의 클래스는 오직 하나의 기능이나 역할에 대한 책임만 가져야 하며, 여러 책임이 섞여 있으면 안 됩니다.
책임의 축이란?
책임은 비즈니스의 요구사항에 따라 구체화됩니다.
책임의 축은 일반적으로 하나의 행위, 도메인 역할, 또는 기능 단위로 정의됩니다.
SRP를 적용하지 않은 경우
문제:
아래 코드에서 OrderManager
클래스는 주문 관리에 필요한 여러 책임을 가지고 있습니다.
주문 생성
결제 처리
영수증 발송
public class OrderManager {
public void createOrder(String productId, int quantity) {
// 주문 생성 로직
System.out.println("Order created for product: " + productId);
}
public void processPayment(String orderId) {
// 결제 처리 로직
System.out.println("Payment processed for order: " + orderId);
}
public void sendReceipt(String email) {
// 영수증 발송 로직
System.out.println("Receipt sent to: " + email);
}
}
결과:
여러 책임이 하나의 클래스에 모여 있어, 변경의 이유가 다양해짐.
예를 들어, 결제 처리 로직이 변경되면 영수증 발송 코드도 위험에 노출될 수 있음.
코드의 테스트 및 유지보수가 어려워짐.
SRP를 적용한 경우
해결:
각 책임을 별도의 클래스로 분리하여, 각 클래스가 단일한 책임만 가지도록 설계합니다.
// 주문 생성 담당
public class OrderService {
public void createOrder(String productId, int quantity) {
System.out.println("Order created for product: " + productId);
}
}
// 결제 처리 담당
public class PaymentService {
public void processPayment(String orderId) {
System.out.println("Payment processed for order: " + orderId);
}
}
// 영수증 발송 담당
public class ReceiptService {
public void sendReceipt(String email) {
System.out.println("Receipt sent to: " + email);
}
}
// 통합 관리
public class OrderManager {
private final OrderService orderService;
private final PaymentService paymentService;
private final ReceiptService receiptService;
public OrderManager(OrderService orderService, PaymentService paymentService, ReceiptService receiptService) {
this.orderService = orderService;
this.paymentService = paymentService;
this.receiptService = receiptService;
}
public void handleOrder(String productId, int quantity, String email) {
orderService.createOrder(productId, quantity);
paymentService.processPayment("12345");
receiptService.sendReceipt(email);
}
}
결과:
각 클래스가 단일 책임만 가짐.
책임이 명확히 분리되어, 하나의 클래스에서 변경이 발생해도 다른 클래스에 영향을 주지 않음.
유지보수성과 테스트 용이성이 크게 향상됨.
SRP 적용의 장점
유지보수성 향상:
각 클래스의 변경 이유가 명확하므로, 수정이 필요한 부분을 쉽게 파악하고 작업 가능.
재사용성 증가:
각 클래스가 독립적인 책임을 가지므로, 다른 곳에서 쉽게 재사용 가능.
테스트 용이성:
각 클래스가 단일 책임만 가지므로, 독립적으로 테스트 가능.
변경의 영향 최소화:
변경의 이유가 단일하므로, 한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화.
SRP와 관련된 잘못된 설계 예
1. Utility Class 남용
모든 기능을 하나의 클래스에 넣는 경우:
public class Utility {
public void calculateDiscount() { /* ... */ }
public void sendEmail() { /* ... */ }
public void generateReport() { /* ... */ }
}
문제점:
클래스가 너무 많은 책임을 가짐.
변경이 발생할 때 다른 코드에 영향을 줄 가능성이 큼.
2. God Class
모든 도메인 로직을 한 곳에 집중:
문제점:
코드가 복잡해지고, 유지보수가 어려워짐.
SRP와 실제 활용
SRP는 특히 대규모 시스템이나 협업 환경에서 유용하며, 다음과 같은 패턴이나 설계 방식에서 자주 사용됩니다:
헥사고날 아키텍처:
각 Port와 Adapter는 단일 책임을 가지도록 설계.
CQRS:
명령(Command)와 조회(Query)를 분리하여 각각 단일 책임을 갖도록 구성.
미들웨어/서비스 계층 설계:
각 서비스 클래스가 단일한 도메인 개념에 집중.
결론
SRP는 객체 지향 설계에서 가장 기본적이면서도 중요한 원칙입니다.
클래스가 단일 책임만 가지도록 설계하면, 유지보수성, 확장성, 테스트 용이성이 향상되고, 시스템의 복잡도를 낮출 수 있습니다. 😊