SOLID > SRP (단일 책임 원칙)
in etc.
본 글은 로버트 C.마틴 저자(송준이 엮음)의 [클린 아키텍처: 소프트웨어 구조와 설계의 원칙] 도서를 참고하였습니다.
1. 의미
- Single responsibility principle.
- 한 클래스는 하나의 책임만 가져야 한다.
- 따라서 각 소프트웨어 모듈은 변경의 이유가 단 하나여야만 한다.
2. 원칙 위반 예제
- 직원 정보를 담당하는 Employee 클래스에는 4가지의 메서드가 있음
- calculatePay() : 회계팀에서 급여를 계산하는 메서드
- reportHours() : 인사팀에서 근무시간을 계산하는 메서드
- saveDababase() : 기술팀에서 변경된 정보를 DB에 저장하는 메서드
- calculateExtraHour() : 초과 근무 시간을 계산하는 메서드 (회계팀과 인사팀에서 공유하여 사용)
각 메서드들은 각 팀에서 필요할 때마다 호출해 사용하고 있음
- 예시 코드에서는 calculatePay() 메서드와 reportHours() 메서드에서 초과 근무 시간을 계산하기 위해 calculateExtraHour() 메서드를 공유하여 사용하고 있음
class Employee { String name; String positon; Employee(String name, String position) { this.name = name; this.positon = position; } // * 초과 근무 시간을 계산하는 메서드 (두 팀에서 공유하여 사용) void calculateExtraHour() { // ... } // * 급여를 계산하는 메서드 (회계팀에서 사용) void calculatePay() { // ... this.calculateExtraHour(); // ... } // * 근무시간을 계산하는 메서드 (인사팀에서 사용) void reportHours() { // ... this.calculateExtraHour(); // ... } // * 변경된 정보를 DB에 저장하는 메서드 (기술팀에서 사용) void saveDababase() { // ... } }
- 회계팀에서 급여를 계산하는 기존의 방식을 새로 변경하여, 코드에서 초과 근무 시간을 계산하는 메서드 calculateExtraHour()의 알고리즘 업데이트가 필요해짐
- 개발팀에서 회계팀의 요청에 따라 calculateExtraHour() 메서드를 변경하였는데, 이에 따른 파급 효과로 인해 의도치 않게 인사팀에서 사용하는 reportHours() 메서드에도 영향을 주게 되버림
- 이렇게 Employee 클래스에서 회계팀, 인사팀, 기술팀 이렇게 3개의 액터에 대한 책임을 한꺼번에 가지고 있기 때문에 SRP에 대해 위배되는 상황
3. 수정한 예제
// * 통합 사용 클래스
class EmployeeFacade {
private String name;
private String positon;
EmployeeFacade(String name, String position) {
this.name = name;
this.positon = position;
}
// * 급여를 계산하는 메서드 (회계팀 클래스를 불러와 에서 사용)
void calculatePay() {
// ...
new PayCalculator().calculatePay();
// ...
}
// * 근무시간을 계산하는 메서드 (인사팀 클래스를 불러와 에서 사용)
void reportHours() {
// ...
new HourReporter().reportHours();
// ...
}
// * 변경된 정보를 DB에 저장하는 메서드 (기술팀 클래스를 불러와 에서 사용)
void EmployeeSaver() {
new EmployeeSaver().saveDatabase();
}
}
// * 회계팀에서 사용되는 전용 클래스
class PayCalculator {
// * 초과 근무 시간을 계산하는 메서드
void calculateExtraHour() {
// ...
}
void calculatePay() {
// ...
this.calculateExtraHour();
// ...
}
}
// * 인사팀에서 사용되는 전용 클래스
class HourReporter {
// * 초과 근무 시간을 계산하는 메서드
void calculateExtraHour() {
// ...
}
void reportHours() {
// ...
this.calculateExtraHour();
// ...
}
}
// * 기술팀에서 사용되는 전용 클래스
class EmployeeSaver {
void saveDatabase() {
// ...
}
}
- 각 책임(기능 담당)에 맞게 클래스를 분리하여 구성함
- 회계팀, 인사팀, 기술팀의 기능 담당은 PayCaculator, HourReporter, EmployeeSaver 각기 클래스로 분리, 이를 통합적으로 사용하는 클래스인 EmployeeFacade 클래스를 만듦
- EmployeeFacade 클래스의 메서드는 사실상 아무런 로직이 없고, 생성자로 인스턴스를 생성하고 각 클래스의 메서드를 사용하는 역할만 수행
- EmployeeFacade 클래스는 어떠한 액터도 담당하지 않게 됨
- 변경 사항이 생겨도 각각의 분리된 클래스에서만 수정하면 되기 때문에 EmployeeFacade 클래스는 냅둬도 기능을 이용하는데 아무런 문제가 생기지 않음
4. SRP 원칙 적용 주의점
4.1. 클래스명은 책임의 소재를 알 수 있게 작명
- 클래스가 하나의 책임을 가지고 있다는 것을 나타내기 위해, 클래스명을 어떠한 기능을 담당하였는지 알 수 있게 작명하는 것이 좋음
- 즉, 각 클래스는 하나의 개념을 나타내게 구성하는 것
4.2. 책임을 분리할 때 항상 결합도, 응집도 따지기
- 결합도와 응집도를 따져가며 클래스를 분리해야 함
- 응집도 : 한 프로그램 요소가 얼마나 뭉쳐있는가를 나타내는 척도
- 결합도 : 프로그램 구성 요소들 사이가 얼마나 의존적인지를 나타내는 척도
- 좋은 프로그램이란 응집도는 높게, 결합도는 낮게 설계하는 것
- 여러가지 책임으로 나눌 때는 각 책임간의 결합도를 최소로 하도록 코드를 구성해야 함
Reference
- 로버트 C.마틴, 2019, 클린 아키텍처: 소프트웨어 구조와 설계의 원칙
- https://ko.wikipedia.org/wiki/SOLID(객체지향_설계)
- https://inpa.tistory.com/entry/OOP-💠-아주-쉽게-이해하는-SRP-단일-책임-원칙