단일 책임 원칙(SRP)이란?
클래스나 모듈은 단 하나의 변경 이유만 가져야 한다.
SRP는 하나의 클래스 또는 모듈이 어려 개의 역할을 담당하면 안 되며, 변경해야 하는 이유가 하나여야 한다는 의미를 가진다. 즉, 클래스는 하나의 명확한 목적을 가져야 하며, 하나의 책임만 수행해야 한다.
쉽게 말해, 하나의 클래스는 하나의 역할만 수행해야 한다. 여러 개의 기능을 한 클래스에서 처리하면 유지보수성이 떨어진다.
SRP가 중요한 이유
코드 유지보수가 쉬워짐: 특정 기능을 변경할 때, 관련 없는 코드에 영향을 주지 않음.
재사용성이 높아짐: 특정 역할을 하는 클래스는 다양한 곳에서 쉽게 재사용할 수 있음.
디버깅 및 테스트가 용이해짐: 하나의 책임만 처리하므로, 단위 테스트가 간결해지고 명확해짐.
협업이 쉬워짐: 여러 사람이 개발할 때, 특정 역할에 대한 코드만 수정하면 되므로 충돌이 줄어듦.
SRP 위반 사례
여러 책임을 가진 클래스
아래 UserManager
클래스는 사용자 권리와 데이터 저장 두 가지 책임을 동시에 수행하고 있다.
class UserManager {
func createUser(name: String, email: String) {
print("사용자 생성: \(name), 이메일: \(email)")
}
func saveUserToDatabase(user: User) {
print("데이터베이스에 사용자 저장: \(user.name)")
}
}
문제점:
UserManager
는 사용자 생성과 데이터 저장이라는 두 가지 책임을 가지고 있음.사용자를 생성하는 로직이 변경되면, 데이터 저장 로직도 함께 영향을 받을 가능성이 있음.
데이터 저장 방식을 변경할 경우,
UserManager
도 함께 수정해야 함.
사용자 관리와 데이터 저장을 분리하여 해결할 수 있다.
SRP 적용 방법
역할을 분리한 클래스 구조
SRP를 적용하여 사용자 관리(UserService)와 데이터 저장(UserRepository)를 분리하면, 각각의 클래스가 하나의 역할만 담당하도록 만들 수 있다.
// 사용자 생성만 담당하는 클래스
class UserService {
func createUser(name: String, email: String) -> User {
print("사용자 생성: \(name), 이메일: \(email)")
return User(name: name, email: email)
}
}
// 데이터 저장만 담당하는 클래스
class UserRepository {
func saveUser(user: User) {
print("데이터베이스에 사용자 저장: \(user.name)")
}
}
UserService: 사용자 생성과 관련된 로직만 관리함.
UserRepository: 데이터 저장과 관련된 로직만 관리함.
역할이 명확해지고, 각 클래스의 변경 이유가 하나만 존재하게 됨.
SRP를 적용하는 일반적인 패턴
SRP를 적용하는 몇 가지 일반적인 패턴을 살펴보자.
MVC 패턴에서 SRP 적용
Model: 데이터 및 비즈니스 로직 처리
View: UI 관련 로직 처리
Controller: 사용자 입력을 받아 Model과 View를 연결
각 요소가 하나의 역할만 담당하므로 SRP를 자연스럽게 적용할 수 있음.
서비스(Service)와 저장소(Repository) 분리
Service: 비즈니스 로직을 수행하는 역할
Repository: 데이터를 저장/조회하는 역할
비즈니스 로직과 데이터 저장을 분리하여 유지보수성을 높일 수 있음.
Delegate 패턴 활용
하나의 클래스가 여러 기능을 담당하는 경우, Delegate를 사용하여 역할을 분리할 수 있음.
protocol AuthenticationDelegate: AnyObject {
func didAuthenticate(user: User)
}
class Authenticator {
weak var delegate: AuthenticationDelegate?
func authenticateUser(email: String, password: String) {
// 로그인 로직 수행
let user = User(name: "John Doe", email: email)
delegate?.didAuthenticate(user: user)
}
}
SRP를 유지하면서도 클래스 간의 결합도를 낮출 수 있음.
SRP를 언제 적용해야 할까?
클래스가 여러 개의 기능을 담당하고 있을 때
한 클래스의 변경이 여러 이유에서 발생할 때
테스트 코드 작성이 어려워질 때
코드가 길어지고 가독성이 떨어질 때