이 프로젝트는 Swift를 사용하여 콘솔 환경에서 실행되는 숫자 야구 게임입니다. 단순한 기능 구현이 아닌 객체 지향 설계 원칙에 따라 코드를 작성하기 위해 고민하고 리팩토링하였습니다.
- 게임 플레이: 컴퓨터가 생성한 중복 없는 세자리 숫자를 맞힙니다.
- 스트라이크/볼 판정: 사용자의 추측에 따라 스트라이크와 볼 개수를 알려줍니다.
- 게임 기록: 각 게임을 할 때마다 시도 횟수를 기록하고, 원할 때 기록을 조회할 수 있습니다.
- 입력 유효성 검사: 규칙에 맞지 않는 입력(문자, 자릿수 오류, 중복 숫자 등)을 처리합니다.
- Swift
- 객체 지향 프로그래밍 (OOP)
- 단일 책임 원칙 (SRP)
- 의존성 주입 (DI)
- 불변성 (Immutability)
- Console Application: 별도의 GUI 없이 터미널 환경에서 실행됩니다.
이 프로젝트에서 신경쓴 부분은 더 나은 코드 구조를 위한 고민과 리팩토링입니다.
초기의 거대했던 BaseballGame 클래스를 각자의 책임에만 집중하는 작은 객체들로 분리했습니다. 이를 통해 코드의 응집도는 높이고 결합도는 낮춰, 유지보수와 테스트가 훨씬 용이해졌습니다.
- GameMenu: 게임 메뉴 표시 및 사용자 선택 처리
- BaseballGame: 게임의 흐름 조율
- NumberGenerator: 정답 숫자 생성
- InputValidator: 사용자 입력 유효성 검사
- GameRecordManager: 게임 기록 저장 및 조회
객체가 필요로 하는 다른 객체(의존성)를 내부에서 직접 생성하지 않고, 외부(생성자)에서 전달받도록 설계했습니다. 이 구조는 테스트 시 객체(Mock Object)를 주입하기 쉽게 만들어 테스트 용이성을 향상시키고, 클래스 간의 결합을 느슨하게 하여 유연성을 높입니다.
// BaseballGame은 필요한 객체들을 외부에서 주입받습니다.
class BaseballGame {
private let numberGenerator: NumberGenerator
// ...
init(numberGenerator: NumberGenerator, ...) {
self.numberGenerator = numberGenerator
// ...
}
}의존성은 세부적인 구현이 아닌 추상화에 의존해야한다는 원칙에 따라 세부 구현 객체에 의존하는 것이 아니라 프로토콜을 만들어서 프로토콜에 의존하여 세부적인 구현 내용에 의존하지 않도록 수정하였습니다.
struct BaseballGame {
//타입을 프로토콜로 정의
private let inputValidator: InputValidatable
private let numberGenerator: NumberGeneratable
private let answerNumber: [Int]
private var guessCount = 0
private let numbersCount: Int
init(inputValidator: InputValidatable, numberGenerator: NumberGeneratable, numbersCount: Int = 3) {
self.inputValidator = inputValidator
self.numberGenerator = numberGenerator
self.answerNumber = numberGenerator.create(numbersCount)
self.numbersCount = numbersCount
}
//생략
}한 게임의 정답(answerNumber)은 절대 바뀌면 안 됩니다. var 대신 let을 사용하여 정답을 불변 상수로 만들고, 객체가 생성될 때 한 번만 할당하도록 강제했습니다.
코드 곳곳에 흩어져 있던 하드코딩된 숫자 3을 numbersCount라는 프로퍼티로 한곳에서 관리하도록 변경했습니다. 덕분에 numbersCount 값만 바꾸면 게임의 정답이 4자리, 5자리 게임으로 쉽게 확장될 수 있는 유연한 구조를 갖추게 되었습니다.