toy-project: 숫자 야구 게임 (3)

jiseong·2022년 6월 15일
0

T I Learned

목록 보기
270/291
post-custom-banner

의도에 맞는 메서드명

다음은 결과 내부에 존재하는 재시작 버튼을 클릭했을 때, 게임을 리셋하는 역할을 하는 메서드이다. 버블링을 활용하여 결과창 DOM 트리 내부에 존재하는 클릭 이벤트를 잡아내고자 처음에는 handleResult라는 이름으로 작성을 하였다.

변경 전

handleResult(event: Event) {
  const target = event.target as HTMLElement;
  if (target.id !== DOM_SELECTOR.RESET_BUTTON.slice(1)) return;

  this.initialize();
}

하지만 메서드명을 보면 결과에 대한 리스너처럼 보여지기 때문에 의도에 맞는 handleReset으로 변경하였다.

변경 후

handleReset(event: Event) {
  const target = event.target as HTMLElement;
  if (target.id !== DOM_SELECTOR.RESET_BUTTON.slice(1)) return;

  this.initialize();
}

분리시켜보기

처음에 작성했던 숫자야구게임 로직은 다음과 같다. 지금이야 큰 문제없이 잘 동작하지만 만약에 어떤 기능이 추가된다면 index.ts 내부에서 모든 것을 담당하고 있어 이 코드는 문제가 발생할 코드이지 않을까 생각된다.

변경 전

// index.ts
class BaseballGame {
  computerInputNumbers: string | undefined;

  userInputNumbers: string | undefined;

  constructor() {
    this.initialize();
    this.addEventDelegation();
  }

  renderResult(markup: string) {
    ($(DOM_SELECTOR.RESULT) as HTMLDivElement).innerHTML = markup;
  }

  play(computerInputNumbers: string, userInputNumbers: string) {
    if (computerInputNumbers === userInputNumbers) return RESULT.ANSWER;
    const hint = getHint(computerInputNumbers, userInputNumbers);

    return hint;
  }

  initialize() {
    this.computerInputNumbers = getComputerInputNumbers();
    this.userInputNumbers = '';

    ($(DOM_SELECTOR.USER_INPUT) as HTMLInputElement).value = '';
    this.renderResult('');
  }

  addEventDelegation() {
    addEventListener(
      $(DOM_SELECTOR.USER_INPUT),
      'input',
      this.handleInput.bind(this),
    );
    addEventListener(
      $(DOM_SELECTOR.SUBMIT_BUTTON),
      'click',
      this.handleSubmit.bind(this),
    );
    addEventListener(
      $(DOM_SELECTOR.RESULT),
      'click',
      this.handleReset.bind(this),
    );
  }

  handleInput(event: Event) {
    this.userInputNumbers = (event.target as HTMLInputElement).value;
  }

  handleSubmit(event: Event) {
    event.preventDefault();
    if (!this.computerInputNumbers || !this.userInputNumbers) return;
    if (!isValid(this.userInputNumbers)) return;

    const result = this.play(this.computerInputNumbers, this.userInputNumbers);
    this.renderResult(result);
  }

  handleReset(event: Event) {
    const target = event.target as HTMLElement;
    if (target.id !== DOM_SELECTOR.RESET_BUTTON.slice(1)) return;

    this.initialize();
  }
}

export default BaseballGame;

new BaseballGame();

어떤 방식으로 나누면 좋을지 고민해봤는데 BaseballGame에 필요한 로직과 상태들 그리고 결과창에 렌더링 되어지는 부분을 나누는 것이 적당해보였다.

변경 후

그래서 먼저 BaseballGame에 필요한 로직과 상태를 분리시켰다. BaseballGame 클래스는 게임이 필요한 상태와 로직을 관리하는 역할을 하며 상태에 접근할 수 있도록 get, set메서드도 추가로 작성해주었다.

// baseballGame.ts

class BaseballGame {
  computerInputNumbers: string | undefined;

  userInputNumbers: string | undefined;

  play(computerInputNumbers: string, userInputNumbers: string) {
    if (computerInputNumbers === userInputNumbers) return RESULT.ANSWER;
    const hint = getHint(computerInputNumbers, userInputNumbers);

    return hint;
  }

  getComputerInputValue() {
    return this.computerInputNumbers;
  }

  setComputerInputValue(value: string) {
    this.computerInputNumbers = value;
  }

  getUserInputValue() {
    return this.userInputNumbers;
  }

  setUserInputValue(value: string) {
    this.userInputNumbers = value;
  }

  resetUserInputValue(value: string) {
    this.setUserInputValue('');
    ($(DOM_SELECTOR.USER_INPUT) as HTMLInputElement).value = value;
  }
}

export default BaseballGame;

다음은 결과화면에 보여질 부분을 분리시켰다. GameResult클래스는 인자로 받은 값을 화면에 렌더해주는 역할을 한다.

// gameResult.ts

class GameResult {
  result: HTMLDivElement;

  constructor() {
    this.result = $(DOM_SELECTOR.RESULT) as HTMLDivElement;
  }

  render(markup: string) {
    this.result.innerHTML = markup;
  }
}

export default GameResult;

마지막으로 위에서 분리한 클래스를 조작할 수 있는 컨트롤 역할의 클래스를 작성해주었다. Controller 클래스에서 게임에 필요한 이벤트 리스너를 등록하고, 호출되었을 때 필요한 로직들을 분리된 클래스를 활용하여 처리하는 역할을 한다.

// controller.ts

class Controller {
  baseballGame: BaseballGame;

  gameResult: GameResult;

  constructor(baseballGame: BaseballGame, gameResult: GameResult) {
    this.baseballGame = baseballGame;
    this.gameResult = gameResult;
    this.initialize();
    this.addEventDelegation();
  }

  initialize() {
    this.baseballGame.setComputerInputValue(getComputerInputNumbers());
    this.baseballGame.resetUserInputValue('');
    this.gameResult.render('');
  }

  addEventDelegation() {
    addEventListener(
      $(DOM_SELECTOR.USER_INPUT),
      'input',
      this.handleInput.bind(this),
    );
    addEventListener(
      $(DOM_SELECTOR.SUBMIT_BUTTON),
      'click',
      this.handleSubmit.bind(this),
    );
    addEventListener(
      $(DOM_SELECTOR.RESULT),
      'click',
      this.handleReset.bind(this),
    );
  }

  handleInput(event: Event) {
    this.baseballGame.setUserInputValue(
      (event.target as HTMLInputElement).value,
    );
  }

  handleSubmit(event: Event) {
    event.preventDefault();
    const computerInputNumbers = this.baseballGame.getComputerInputValue();
    const userInputNumbers = this.baseballGame.getUserInputValue();

    if (!computerInputNumbers || !userInputNumbers) return;
    if (!isValid(userInputNumbers)) return;

    const result = this.baseballGame.play(
      computerInputNumbers,
      userInputNumbers,
    );
    this.gameResult.render(result);
  }

  handleReset(event: Event) {
    const target = event.target as HTMLElement;
    if (target.id !== DOM_SELECTOR.RESET_BUTTON.slice(1)) return;

    this.initialize();
  }
}

export default Controller;
// index.ts
const baseballGame = new BaseballGame();
const gameResult = new GameResult();

new Controller(baseballGame, gameResult);
post-custom-banner

0개의 댓글