[내일배움캠프 11일차] 야구게임 만들기 Lv1, Lv2

NH·2025년 3월 17일

내일배움캠프

목록 보기
11/62
post-thumbnail

📝 TIL - Swift 야구게임 만들기

🎯 오늘 학습한 내용

오늘은 Swift로 야구 게임(BaseballGame)을 개발하면서 객체지향 프로그래밍(OOP) 원칙을 적용하는 과정을 정리했다.
특히 SOLID 원칙 중 의존성 역전 원칙(DIP, Dependency Inversion Principle)을 준수하는 방법을 고민하며 리팩토링했다.


🚀 1. SOLID 원칙 적용

지난 과제에서 객체지향 프로그래밍을 공부하여, 이번 과제에서도 객체지향 개발 원칙을 고려하며 설계를 개선했다.

SRP (단일 책임 원칙, Single Responsibility Principle)

  • RandomNumberGenerator, UserInput, CheckAnswer, MessageOutput 등으로 기능을 분리하여 각 객체가 하나의 책임만 가지도록 설계했다.

OCP (개방-폐쇄 원칙, Open-Closed Principle)

  • UserInputtable 프로토콜을 사용하여 입력 방식의 확장성을 고려했다.
    다른 기능을 추가 하더라도 BaseballGame을 수정하지 않아도 된다.

DIP (의존성 역전 원칙, Dependency Inversion Principle)

  • 저번 과제에서도 적용 시켰던 의존성 역전 원칙을 적용했다.
  • BaseballGame이 구체적인 클래스(UserInput)가 아니라 추상화(UserInputtable 프로토콜)에 의존하도록 개선했다.
  • 이를 통해 유연성과 테스트 용이성이 향상되었다.

🔧 2. 리팩토링 과정

1) 개발 시작

단일 책임 원칙을 지키기 위해 처음에 개발할 때, 모든 기능을 func 단위로 작성했고,
모든 함수는 하나의 역할을 수행하도록 나눴다.

기능 목록은 아래와 같다.

  • 랜덤 숫자 생성 함수
  • 사용자 숫자 입력 함수
  • 정답 체크 함수
  • 스트라이크, 볼 갯수 확인 함수
  • 게임 실행 함수

2) 코드 구조 리팩토링

  1. 작성한 함수들을 객체로 만들기 위해, 모두 Struct로 리팩토링을 진행했다.
  2. 스트라이크와 볼 수를 체크하는 함수를 게임 실행 함수와 병합했다.
    • 사유
      • 게임이 실행되면서 자연스럽게 스트라이크와 볼 수를 체크하게 됨
      • 게임 실행 함수에서 스트라이크와 볼 수를 체크하는 것이 바람직하다는 판단

3) 의존성 역전 원칙

기존 코드에서는 모든 구조체들을 BaseballGame에서 직접 생성해서 사용하고 있었다.

let userInput = UserInput()
이렇게 하면 BaseballGameUserInput의 구체적인 구현에 의존하게 되어 DIP를 위반하게 된다.

따라서 프로토콜을 도입하여 모든 객체에 대해서 아래와 같이 추상화했다.

    1. UserInputtable 프로토콜 생성
      protocol UserInputtable {
         func getUserInput() -> [Int]
      }
      👉 이제 BaseballGame은 UserInputtable 프로토콜에 의존하게 된다.
    1. 기존 UserInput을 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을 따르도록 구현 완료
    1. BaseballGame에서 직접 생성한 객체 -> 프로토콜을 사용해 추상화
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를 준수

🎯 3. 최종 정리

✅ 직접 객체 생성 방법에서 프로토콜(UserInputtable)로 추상화하여 DIP 적용
✅ BaseballGame이 구체적인 클래스 및 구조체가 아닌 인터페이스(UserInputtable)에 의존하도록 개선
✅ 테스트를 위해 MockUserInput을 쉽게 만들 수 있도록 설계

이번 리팩토링을 통해 객체지향 프로그래밍 원칙(SOLID)을 적용하는 중요성을 배웠다!
특히 DIP를 준수하여 코드의 확장성과 유지보수성을 높이는 방법을 직접 경험할 수 있었다. 🚀🔥

profile
iOS 개발 블로그

0개의 댓글