WHAT) 프로그램의 유지보수 성을 높이기 위한 설계 기법으로, 클래스(객체)는 단 하나의 책임(기능)만 가져야 한다는 원칙
즉, 하나의 클래스는 하나의 기능을 담당하여 하나의 책임을 수행하는 데 집중되도록 클래스를 따로따로 여러개 설계하라는 원칙
WHY) 하나의 클래스에 기능(책임)이 여러개 있다면 기능 변경(수정)이 일어났을 때 수정해야할 코드가 많아진다.
즉, SRP 원칙을 따름으로써 한 책임의 변경으로부터 다른 책임의 변경으로의 연쇄작용을 극복할 수 있게 된다.
class Employee(val name: String, val position: String) {
// 초과 근무 시간을 계산하는 메서드 (두 팀에서 공유하여 사용)
fun calculateExtraHour() { /*...*/ }
// 급여를 계산하는 메서드 (회계팀에서 사용)
fun calculatePay() {
calculateExtraHour()
}
// 근무시간을 계산하는 메서드 (인사팀에서 사용)
fun reportHours() {
calculateExtraHour()
}
// 변경된 정보를 DB에 저장하는 메서드 (기술팀에서 사용)
fun saveDatabase() { /*...*/ }
}
✅ 액터 : 시스템을 수행하는 역할을 하는 요소로서, 시스템을 이용하는 사용자, 하드웨어 혹은 외부 시스템이 될 수 있다.
// 통합 사용 클래스
class EmployeeFacade(private val name: String, private val position: String) {
// 급여를 계산하는 메서드 (회계팀 클래스를 불러와서 사용)
fun calculatePay() {
PayCalculator().calculatePay()
}
// 근무시간을 계산하는 메서드 (인사팀 클래스를 불러와서 사용)
fun reportHours() {
HourReporter().reportHours()
}
// 변경된 정보를 DB에 저장하는 메서드 (기술팀 클래스를 불러와서 사용)
fun employeeSaver() {
EmployeeSaver().saveDatabase()
}
}
// 회계팀에서 사용되는 전용 클래스
class PayCalculator {
// 초과 근무 시간을 계산하는 메서드
fun calculateExtraHour() { /*...*/ }
fun calculatePay() {
calculateExtraHour()
}
}
// 인사팀에서 사용되는 전용 클래스
class HourReporter {
// 초과 근무 시간을 계산하는 메서드
fun calculateExtraHour() { /*...*/ }
fun reportHours() {
calculate, ExtraHour()
}
}
// 기술팀에서 사용되는 전용 클래스
class EmployeeSaver {
fun saveDatabase() { /*...*/ }
}
class EmployeeManagement {
// Create 작업을 담당하는 CRUD 메소드
fun addEmployee(employee: String) {
if (employee == "") {
postServer(employee) // 서버에 직원 정보를 보냄
logResult("[LOG] EMPLOYEE ADDED") // 로그 출력
} else {
logResult("[ERROR] NAME MUST NOT BE EMPTY")
}
}
// 서버에 데이터를 전달하는 메소드
fun postServer(employees: String) {}
// 로그를 출력하는 메소드
fun logResult(message: String) {
println(message) // 로그를 콘솔에 출력
writeToFile(message) // 로그 내용을 로그 파일에 저장
}
// 파일에 내용을 저장하는 메소드
fun writeToFile(msg: String) {}
}
class EmployeeManagement {
private val logger = Logger() // 합성
// Create 작업을 담당하는 CRUD 메소드
fun addEmployee(employee: String) {
if (employee == "") {
postServer(employee) // 서버에 직원 정보를 보냄
logger.logResult("[LOG] EMPLOYEE ADDED") // 로그 출력
} else {
logger.logResult("[ERROR] NAME MUST NOT BE EMPTY")
}
}
// 서버에 데이터를 전달하는 메소드
fun postServer(employees: String) {}
}
class Logger {
// 로그를 출력하는 메소드
fun logResult(message: String) {
println(message) // 로그를 콘솔에 출력
writeToFile(message) // 로그 내용을 로그 파일에 저장
}
// 파일에 내용을 저장하는 메소드
fun writeToFile(msg: String) {}
}
어떤 프로그램을 개발하느냐에 따라 개발자의 생각이 제각기 다르기 때문에 단일 책임 원칙에 100% 답은 없다. 하지만 중요한 것은 클래스를 작성할 때 단일 책임 원칙을 지켰는지 끊임 없이 생각해보는 것이다. 하나의 클래스가 너무 많은 책임을 가지진 않았는지, 분리할 수 있는 변수와 메소드가 많은 것은 아닌지를 항상 고민해 봐야 한다.
클래스가 하나의 책임을 가지고 있다는 것을 나타내기 위해, 클래스명으로 어떠한 기능을 담당하는지 알 수 있게 작명하는 것이 좋다.
응집도 : 한 프로그램 요소가 얼마나 뭉쳐있는가를 나타내는 척도
결합도 : 프로그램 구성 요소들 사이가 얼마나 의존적인지를 나타내는 척도
좋은 프로그램이란 응집도를 높게, 결합도는 낮게 설계하는 것
따라서 여러가지 책임으로 나눌 때는 각 책임 간의 결합도를 최소로 하도록 코드를 구성해야 한다.
하지만 그 반대로 너무 많은 책임 분할로 인하여 책임이 여러군데로 파편화되어있는 경우에는 산탄총 수술로 다시 응집력을 높여주는 작업이 추가로 필요하다.