좋은 설계란 기본적으로 시스템에 새로운 요구사항이나 변경이 있을 때 가능한 한 영향 받는 부분을 최소화한 설계이다.
"변경" 관점에서 이유를 찾을 수 있다.
책임이 많아질수록 서로 다른 역할을 수행하는 코드끼리 강하게 결합될 가능성이 높아지게 되어 시스템이 복잡해질 수 있다. 즉 결합도가 상승하게 되는데 이는 객체지향 설계의 핵심과 거리가 멀다.
그래서 만약 기능에 변경사항이 생기면 이 기능을 사용하는 부분의 코드를 모두 다시 테스트를 해야 할 수도 있다.
또한 결합도가 높기때문에 해당 기능만 수행하는 메서드의 테스트가 힘들어 질수도 있다.
class WorkoutResult {
func workout() -> String {
let wods = announce()
let doWorkout = workout(workouts: wods)
let result = judge(isDone: doWorkout) ? "오운완" : "노랩천국"
return result
}
private func announce() -> [String] {
return ["10 Snatch", "10 Burpee"]
}
private func workout(workouts: [String]) -> [Bool] {
return Array(repeating: true, count: workouts.count)
}
private func judge(isDone: [Bool]) -> Bool {
let cnt = isDone.filter { $0 == true }.count
let judgeResult = cnt == isDone.count ? true : false
return judgeResult
}
}
let miori = WorkoutResult()
miori.workout()
위의 코드를 본다면, 클래스 내부에서 서로 다른 기능(역할)을 하는 함수끼리 결합이 되어있다.
만약, 운동을 뭘 할지 알려줄 announce
메서드나 운동을 제대로 수행했는지 확인하는 judge
함수를 바꿔야 한다면 해당클래스에서 계속 수정을 해야한다.
즉 SRP를 위배하게 된다.
따라서 책임을 분리시켜, 한 클래스에서 하나의 책임을 가질 수 있도록 수정해야 OOP가 지향하는 설계에 가까워질수 있다.
protocol AnnounceProtocol {
func announce() -> [String]
}
protocol WorkoutProtocol {
func workout(workouts: [String]) -> [Bool]
}
protocol JudgeProtocol {
func judge(isDone: [Bool]) -> Bool
}
class SRPWorkoutResult {
let announce: AnnounceProtocol
let workouts: WorkoutProtocol
let judge: JudgeProtocol
func workout() -> String {
let wods = announce.announce()
let doWorkout = workouts.workout(workouts: wods)
let result = judge.judge(isDone: doWorkout) ? "오운완" : "노랩천국"
return result
}
init(announce: AnnounceProtocol, workouts: WorkoutProtocol, judge: JudgeProtocol) {
self.announce = announce
self.workouts = workouts
self.judge = judge
}
}
class Announce: AnnounceProtocol {
func announce() -> [String] {
return ["10 Snatch", "10 Burpee"]
}
}
class Workout: WorkoutProtocol {
func workout(workouts: [String]) -> [Bool] {
return Array(repeating: true, count: workouts.count)
}
}
class Judge: JudgeProtocol {
func judge(isDone: [Bool]) -> Bool {
let cnt = isDone.filter { $0 == true }.count
let judgeResult = cnt == isDone.count ? true : false
return judgeResult
}
}
let miori2 = SRPWorkoutResult(announce: Announce(), workouts: Workout(), judge: Judge())
miori2.workout()
위의 코드처럼, 운동을 뭘할지 알려주는 기능 / 운동을 하는 기능 / 운동을 잘하고 있는지 judge를 봐주는 기능 을 나눈다면 결합도는 낮아지게 된다.
그리고 하나의 역할만 하는 기능들끼리 모여있기 때문에 응집도는 높아지게 된다.