오늘은 Swift로 야구 게임(BaseballGame)을 개발하면서 객체지향 프로그래밍(OOP) 원칙을 적용하는 과정을 정리했다.
특히 SOLID 원칙 중 의존성 역전 원칙(DIP, Dependency Inversion Principle)을 준수하는 방법을 고민하며 리팩토링했다.
지난 과제에서 객체지향 프로그래밍을 공부하여, 이번 과제에서도 객체지향 개발 원칙을 고려하며 설계를 개선했다.
RandomNumberGenerator, UserInput, CheckAnswer, MessageOutput 등으로 기능을 분리하여 각 객체가 하나의 책임만 가지도록 설계했다.UserInputtable 프로토콜을 사용하여 입력 방식의 확장성을 고려했다.BaseballGame을 수정하지 않아도 된다.BaseballGame이 구체적인 클래스(UserInput)가 아니라 추상화(UserInputtable 프로토콜)에 의존하도록 개선했다.단일 책임 원칙을 지키기 위해 처음에 개발할 때, 모든 기능을 func 단위로 작성했고,
모든 함수는 하나의 역할을 수행하도록 나눴다.
기능 목록은 아래와 같다.
Struct로 리팩토링을 진행했다.기존 코드에서는 모든 구조체들을 BaseballGame에서 직접 생성해서 사용하고 있었다.
let userInput = UserInput()
이렇게 하면 BaseballGame이 UserInput의 구체적인 구현에 의존하게 되어 DIP를 위반하게 된다.
protocol UserInputtable {
func getUserInput() -> [Int]
}👉 이제 BaseballGame은 UserInputtable 프로토콜에 의존하게 된다.struct UserInput: UserInputtable {
func getUserInput() -> [Int] {
while true {
print("세 자리 숫자를 입력하세요: ", terminator: "")
guard let userInputString = readLine(), !userInputString.isEmpty else {
print("Error!! 숫자를 입력하세요.")
continue
}
guard let _ = Int(userInputString) else {
print("Error!! 숫자를 입력하세요.")
continue
}
guard userInputString.count == 3 else {
print("Error!! 세 자리 숫자가 아닙니다.")
continue
}
let userInputArray = userInputString.compactMap { Int(String($0)) }
guard Set(userInputArray).count == 3 else {
print("Error!! 숫자가 중복되지 않아야 합니다.")
continue
}
return userInputArray
}
}
}👉 UserInput이 UserInputtable을 따르도록 구현 완료class BaseballGame {
// 기존 직접 객체 생성 -> 프로토콜을 사용하여 추상화 사용
let message: MessageOutput
let randomNumberGenerator: RandomNumberGeneratable
let userInput: UserInputtable
let checkAnswer: AnswerCheckable
init(message: MessageOutput,
randomNumberGenerator: RandomNumberGeneratable,
userInput: UserInputtable,
checkAnswer: AnswerCheckable) {
self.message = message
self.randomNumberGenerator = randomNumberGenerator
self.userInput = userInput
self.checkAnswer = checkAnswer
}
func startGame() {
message.start()
let gameAnswer = randomNumberGenerator.generate()
while true {
message.input()
let userAnswer = userInput.getUserInput()
let result = checkAnswer.check(userInput: userAnswer, correctAnswer: gameAnswer)
if result.strike == 3 {
message.success()
break
} else if result.strike == 0 && result.ball == 0 {
message.loss()
} else {
message.resultMessage(strike: result.strike, ball: result.ball)
}
}
}
}
👉 이제 BaseballGame이 구체적인 UserInput이 아니라 UserInputtable에 의존하므로 DIP를 준수
✅ 직접 객체 생성 방법에서 프로토콜(UserInputtable)로 추상화하여 DIP 적용
✅ BaseballGame이 구체적인 클래스 및 구조체가 아닌 인터페이스(UserInputtable)에 의존하도록 개선
✅ 테스트를 위해 MockUserInput을 쉽게 만들 수 있도록 설계
이번 리팩토링을 통해 객체지향 프로그래밍 원칙(SOLID)을 적용하는 중요성을 배웠다!
특히 DIP를 준수하여 코드의 확장성과 유지보수성을 높이는 방법을 직접 경험할 수 있었다. 🚀🔥