프리코스 - 2주차 리팩토링

정민주·2025년 10월 29일

오늘은 2주차 미션에서 피드백 받은 리뷰들로, 리팩토링을 진행했다.🧰
(리팩토링 과정은, 미션이 끝난 후 공부를 위해 하는 것이기에 좀 과하게 보일 수도 있다!)

그 중에서도 메인 피드백인 3️⃣번 과정에 대해서만 정리해 보고자 한다!!
다른 피드백들은 아래에 간략하게 정리해놓겠다.

1. 피드백 요약본

  • 1️⃣ static import 지양

    • static import를 과도하게 사용하면 상수나 메서드의 소속 클래스가 불분명해짐.
  • 2️⃣ 매직 넘버 상수 처리 및 초기화 불필요

    • int 타입 필드는 JVM에서 자동으로 0으로 초기화되므로 score = 0은 생략 가능.
  • 3️⃣ Move 필드는 Strategy 패턴으로 확장 가능
    • CarMove를 필드로 포함하는 구조는 전략 패턴의 전형적인 형태.
    • 다형성을 활용해 다양한 이동 전략(RandomMove, AlwaysMove 등)을 주입 가능.
  • 4️⃣ 도메인과 입출력 로직 분리

    • 현재 RacingCar 내부에 입력 검증 함수와 출력 담당 코드가 혼재되어 있음.
    • MVC 원칙에 따라 도메인과 입출력 클래스를 분리하여 SRP(단일 책임 원칙) 준수.
  • 5️⃣ 상수 관리 위치 명확히 (Single Source of Truth)

    • 여러 클래스에서 공통으로 쓰이는 상수는 별도의 Constants 클래스에서 관리.
    • 특정 클래스에서만 쓰이는 상수는 해당 클래스 내부에 위치시키는 것이 효율적.
    • 참고: 상수(static final)는 JVM 메모리에 한 번만 로드됨.
  • 6️⃣ Cars 내부의 중복 이름 저장 개선

    • 현재 Set<String>으로 이름을 중복 확인하지만 불필요한 중복 구조.
    • Stream API의 distinct()를 활용해 중복 제거 가능.
  • 7️⃣ 접근자(Access Modifier) 주의


2. Strategy 패턴 적용 시도 및 문제점

Strategy 패턴?

GoF의 디자인 패턴 원문에서 Strategy의 소개 맨 윗줄에 다음과 같은 부분이 있다.

-> 알고리즘의 family를 정의하고,
-> 각각의 알고리즘을 캡슐화하며
-> 그것들을 교체 가능하게 만든다!
(해당 내용은 디스코드 토론방을 열심히 구경하다 발견했습니다👀)

현재 내 코드는 다음과 같은 구조로 설계되어 있었다.
(Move 강조를 위해 상관없는 클래스, 필드, 함수는 생략)

처음에는 DIP(의존성 역전), OCP(개방-폐쇄 원칙), 그리고 합성(Composition)까지 고려한 꽤 괜찮은 설계처럼 보였다.
하지만 곧 다음과 같은 문제가 드러났다.

  • Move 필드의 중복 보관
  • Cars 클래스의 단일 책임 원칙(SRP) 위반 — Car 객체의 관리 역할에 로직이 섞여 있음

그래서 Move 필드를 Cars에서 제거하고 Car로 이동시켜봤다.
하지만 이번엔 새로운 문제가 생겼다. 🙊

  • Move 전략 변경을 위해 set 메서드가 필요
  • 이로 인해 객체의 캡슐화가 깨짐

결국, 캡슐화를 지키면서도 OCP를 만족할 수 있는 새로운 설계를 고민할 수 밖에 없었다. ㅠ-ㅠ

3. 전략 패턴의 재정립

전략의 관리 책임이 도메인 내에 있으니, 이를 변경할때 ocp가 자동적으로 위반되는 구조였다!

그렇다면 전략을 관리하는 책임만 있는 클래스를 만들면 되지 않을까?

그래서 아래와 같은 클래스를 작성해주게 되었다.

MoveStrategyRegistry 클래스 하는 일

  • 생성자로 전달받은 List<Move>를 순회하며, 각 전략의 getKey() 값을 기준으로 Map<String, Move> 형태로 저장한다.

즉 Move 전략을 <key, value>로 관리하는 클래스이다!

이렇게 되면, 당연히 각 Move 클래스는 각자의 키를 고유적으로 갖고 있어야 한다.

그래서 Move 인터페이스를 다음과 같이 수정하고, 해당 인터페이스를 상속받은 구현 클래스들은 내부적으로 key를 반환하면 된다.

이제 Application 클래스의 main에서 MoveStrategyRegistry 클래스 생성자 인수로 우리가 가진 Move 구현체들을 넣어주면 초기 세팅이 끝나는 것이다!!!

그리고 전략을 바꿔주는 역할은 사용자에게 넘겼다.
그렇기에 우리는 어떤 전략을 사용할지 입력만 해주면 손쉽게 해당 전략이 적용된 레이싱을 볼 수 있게 되었다.

참고로 DOUBLE이란 전략은, 내가 그냥 하나 만들어보았다ㅎ
랜덤한 수가 8 이상이 나왔을 시 2칸 전진, 미만일 경우 1칸 전진이라는 규칙을 가진다.

3. 최종 변환

이제 다음과 같은 클래스 다이어그램으로 변환되었다. 이젠 다음과 같은 장점을 가진다!

  • SRP 강화
    → 전략 관리(등록·선택)를 MoveStrategyRegistry로 분리해 역할이 명확해짐.
    → Move 전략을 Car 객체만 보유.
  • OCP 충족
    → 새 전략 추가 시 기존 코드 수정 없이 확장 가능.
  • 결합도 감소
    → 전략 교체가 간접적으로 이루어져 유지보수 쉬워짐.

해당 리팩토링의 결과는 깃허브 주소에서 볼 수 있다.

사실 아직 Model과 view의 분리라는 거대한 리팩토링도.. 남아있다ㅠㅠ 하지만 이번주차는 많은 걸 얻었기에 여기서 리팩토링을 종료해보려고 한다. 🏃


참고글

0개의 댓글